From 9eed2f034497c29d8e6623d706f062fc49a0c89f Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Tue, 28 Jan 2020 01:07:54 +0100 Subject: [PATCH 01/62] fast-dev-hack --- src/Directory.Build.props | 2 +- src/ImageSharp/ImageSharp.csproj | 3 ++- tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj | 3 ++- .../ImageSharp.Tests.ProfilingSandbox.csproj | 3 ++- tests/ImageSharp.Tests/ImageSharp.Tests.csproj | 3 ++- 5 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 5e3f9b061..39cd51ded 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -23,7 +23,7 @@ - true + diff --git a/src/ImageSharp/ImageSharp.csproj b/src/ImageSharp/ImageSharp.csproj index 0fd449d90..f503ea64a 100644 --- a/src/ImageSharp/ImageSharp.csproj +++ b/src/ImageSharp/ImageSharp.csproj @@ -10,7 +10,8 @@ $(packageversion) 0.0.1 - netcoreapp3.1;netcoreapp2.1;netstandard2.1;netstandard2.0;netstandard1.3;net472 + + netcoreapp3.1 true true diff --git a/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj b/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj index 60b1fde8e..198f2fb76 100644 --- a/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj +++ b/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj @@ -5,7 +5,8 @@ ImageSharp.Benchmarks Exe SixLabors.ImageSharp.Benchmarks - netcoreapp3.1;netcoreapp2.1;net472 + + netcoreapp3.1 false false diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj b/tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj index 99269e339..9ac20c078 100644 --- a/tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj +++ b/tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj @@ -8,7 +8,8 @@ false SixLabors.ImageSharp.Tests.ProfilingSandbox win7-x64 - netcoreapp3.1;netcoreapp2.1;net472 + + netcoreapp3.1 SixLabors.ImageSharp.Tests.ProfilingSandbox.Program false diff --git a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj index 34cdca49a..fdefa38e7 100644 --- a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj +++ b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj @@ -2,7 +2,8 @@ - netcoreapp3.1;netcoreapp2.1;net472 + + netcoreapp3.1 True True SixLabors.ImageSharp.Tests From d90ece2729071cdfb0dae35ea098bed43eee1afd Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Tue, 28 Jan 2020 02:20:12 +0100 Subject: [PATCH 02/62] initial skeleton of discontinuous buffer object model --- src/ImageSharp/ImageSharp.csproj | 6 +- .../Allocators/ArrayPoolMemoryAllocator.cs | 10 ++- .../Memory/Allocators/MemoryAllocator.cs | 5 ++ .../Allocators/SimpleGcMemoryAllocator.cs | 5 +- src/ImageSharp/Memory/BufferArea{T}.cs | 2 +- .../IUniformMemoryGroup{T}.cs | 15 ++++ .../InvalidMemoryOperationException.cs | 8 ++ .../UniformMemoryGroupView{T}.cs | 76 +++++++++++++++++++ .../UniformMemoryGroup{T}.Allocated.cs | 69 +++++++++++++++++ .../UniformMemoryGroup{T}.External.cs | 37 +++++++++ .../UniformMemoryGroup{T}.cs | 40 ++++++++++ .../TestUtilities/TestMemoryAllocator.cs | 4 +- 12 files changed, 270 insertions(+), 7 deletions(-) create mode 100644 src/ImageSharp/Memory/DiscontinuousProto/IUniformMemoryGroup{T}.cs create mode 100644 src/ImageSharp/Memory/DiscontinuousProto/InvalidMemoryOperationException.cs create mode 100644 src/ImageSharp/Memory/DiscontinuousProto/UniformMemoryGroupView{T}.cs create mode 100644 src/ImageSharp/Memory/DiscontinuousProto/UniformMemoryGroup{T}.Allocated.cs create mode 100644 src/ImageSharp/Memory/DiscontinuousProto/UniformMemoryGroup{T}.External.cs create mode 100644 src/ImageSharp/Memory/DiscontinuousProto/UniformMemoryGroup{T}.cs diff --git a/src/ImageSharp/ImageSharp.csproj b/src/ImageSharp/ImageSharp.csproj index f503ea64a..0d803475a 100644 --- a/src/ImageSharp/ImageSharp.csproj +++ b/src/ImageSharp/ImageSharp.csproj @@ -25,9 +25,9 @@ - - - + + + diff --git a/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.cs index c4d92ca3c..57a5b77bc 100644 --- a/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.cs +++ b/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.cs @@ -89,6 +89,14 @@ namespace SixLabors.ImageSharp.Memory this.InitArrayPools(); } + /// + /// Gets or sets the length of the largest contiguous buffer that can be handled by this allocator instance. + /// + public int MaximumContiguousBufferLength { get; set; } = Int32.MaxValue; + + /// + protected internal override int GetMaximumContiguousBufferLength() => this.MaximumContiguousBufferLength; + /// public override IMemoryOwner Allocate(int length, AllocationOptions options = AllocationOptions.None) { @@ -147,4 +155,4 @@ namespace SixLabors.ImageSharp.Memory this.normalArrayPool = ArrayPool.Create(this.PoolSelectorThresholdInBytes, this.maxArraysPerBucketNormalPool); } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs index 20598c3e3..1e1f69784 100644 --- a/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs +++ b/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs @@ -10,6 +10,11 @@ namespace SixLabors.ImageSharp.Memory /// public abstract class MemoryAllocator { + /// + /// Gets the length of the largest contiguous buffer that can be handled by this allocator instance. + /// + protected internal abstract int GetMaximumContiguousBufferLength(); + /// /// Allocates an , holding a of length . /// diff --git a/src/ImageSharp/Memory/Allocators/SimpleGcMemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/SimpleGcMemoryAllocator.cs index 54b64b131..ee9afbac5 100644 --- a/src/ImageSharp/Memory/Allocators/SimpleGcMemoryAllocator.cs +++ b/src/ImageSharp/Memory/Allocators/SimpleGcMemoryAllocator.cs @@ -11,6 +11,9 @@ namespace SixLabors.ImageSharp.Memory /// public sealed class SimpleGcMemoryAllocator : MemoryAllocator { + /// + protected internal override int GetMaximumContiguousBufferLength() => int.MaxValue; + /// public override IMemoryOwner Allocate(int length, AllocationOptions options = AllocationOptions.None) { @@ -27,4 +30,4 @@ namespace SixLabors.ImageSharp.Memory return new BasicByteBuffer(new byte[length]); } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Memory/BufferArea{T}.cs b/src/ImageSharp/Memory/BufferArea{T}.cs index 08731846e..ec7665998 100644 --- a/src/ImageSharp/Memory/BufferArea{T}.cs +++ b/src/ImageSharp/Memory/BufferArea{T}.cs @@ -9,7 +9,7 @@ namespace SixLabors.ImageSharp.Memory /// Represents a rectangular area inside a 2D memory buffer (). /// This type is kind-of 2D Span, but it can live on heap. /// - /// The element type + /// The element type. internal readonly struct BufferArea where T : struct { diff --git a/src/ImageSharp/Memory/DiscontinuousProto/IUniformMemoryGroup{T}.cs b/src/ImageSharp/Memory/DiscontinuousProto/IUniformMemoryGroup{T}.cs new file mode 100644 index 000000000..e6c71fa55 --- /dev/null +++ b/src/ImageSharp/Memory/DiscontinuousProto/IUniformMemoryGroup{T}.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; + +namespace SixLabors.ImageSharp.Memory.DiscontinuousProto +{ + /// + /// Represents a group of one or more uniformly-sized discontinuous memory segments. + /// The last segment can be smaller than the preceding ones. + /// + /// The element type. + public interface IUniformMemoryGroup : IReadOnlyList> where T : struct + { + bool IsValid { get; } + } +} diff --git a/src/ImageSharp/Memory/DiscontinuousProto/InvalidMemoryOperationException.cs b/src/ImageSharp/Memory/DiscontinuousProto/InvalidMemoryOperationException.cs new file mode 100644 index 000000000..f756a1246 --- /dev/null +++ b/src/ImageSharp/Memory/DiscontinuousProto/InvalidMemoryOperationException.cs @@ -0,0 +1,8 @@ +using System; + +namespace SixLabors.ImageSharp.Memory.DiscontinuousProto +{ + public class InvalidMemoryOperationException : InvalidOperationException + { + } +} diff --git a/src/ImageSharp/Memory/DiscontinuousProto/UniformMemoryGroupView{T}.cs b/src/ImageSharp/Memory/DiscontinuousProto/UniformMemoryGroupView{T}.cs new file mode 100644 index 000000000..68ef5c25d --- /dev/null +++ b/src/ImageSharp/Memory/DiscontinuousProto/UniformMemoryGroupView{T}.cs @@ -0,0 +1,76 @@ +using System; +using System.Buffers; +using System.Collections; +using System.Collections.Generic; + +namespace SixLabors.ImageSharp.Memory.DiscontinuousProto +{ + /// + /// Implements , defining a view for + /// rather than owning the segments. + /// + /// + /// This type provides an indirection, protecting the users of publicly exposed memory API-s + /// from internal memory-swaps. Whenever an internal swap happens, the + /// instance becomes invalid, throwing an exception on all operations. + /// + /// The element type. + public class UniformMemoryGroupView : IUniformMemoryGroup where T : struct + { + private readonly UniformMemoryGroup owner; + private readonly MemoryOwnerWrapper[] memoryWrappers; + + public UniformMemoryGroupView(UniformMemoryGroup owner) + { + this.IsValid = true; + this.owner = owner; + this.memoryWrappers = new MemoryOwnerWrapper[owner.Count]; + + for (int i = 0; i < owner.Count; i++) + { + this.memoryWrappers[i] = new MemoryOwnerWrapper(this, i); + } + } + + public IEnumerator> GetEnumerator() => throw new NotImplementedException(); + + IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); + + public int Count { get; } + + public Memory this[int index] => throw new NotImplementedException(); + + public bool IsValid { get; internal set; } + + class MemoryOwnerWrapper : MemoryManager + { + private UniformMemoryGroupView view; + + private int index; + + public MemoryOwnerWrapper(UniformMemoryGroupView view, int index) + { + this.view = view; + this.index = index; + } + + protected override void Dispose(bool disposing) + { + } + + public override Span GetSpan() + { + if (!this.view.IsValid) + { + throw new InvalidOperationException(); + } + + return this.view[this.index].Span; + } + + public override MemoryHandle Pin(int elementIndex = 0) => throw new NotImplementedException(); + + public override void Unpin() => throw new NotImplementedException(); + } + } +} diff --git a/src/ImageSharp/Memory/DiscontinuousProto/UniformMemoryGroup{T}.Allocated.cs b/src/ImageSharp/Memory/DiscontinuousProto/UniformMemoryGroup{T}.Allocated.cs new file mode 100644 index 000000000..b9157b59d --- /dev/null +++ b/src/ImageSharp/Memory/DiscontinuousProto/UniformMemoryGroup{T}.Allocated.cs @@ -0,0 +1,69 @@ +using System; +using System.Buffers; +using System.Collections.Generic; +using System.Linq; + +namespace SixLabors.ImageSharp.Memory.DiscontinuousProto +{ + public abstract partial class UniformMemoryGroup + { + private class Allocated : UniformMemoryGroup + { + private IMemoryOwner[] memoryOwners; + + public Allocated(IMemoryOwner[] memoryOwners) + { + this.memoryOwners = memoryOwners; + } + + public override IEnumerator> GetEnumerator() + { + this.EnsureNotDisposed(); + return this.memoryOwners.Select(mo => mo.Memory).GetEnumerator(); + } + + + public override int Count + { + get + { + this.EnsureNotDisposed(); + return this.memoryOwners.Length; + } + } + + public override Memory this[int index] + { + get + { + this.EnsureNotDisposed(); + return this.memoryOwners[index].Memory; + } + } + + public override void Dispose() + { + if (this.memoryOwners == null) + { + return; + } + + foreach (IMemoryOwner memoryOwner in this.memoryOwners) + { + memoryOwner.Dispose(); + } + + this.memoryOwners = null; + this.IsValid = false; + } + + private void EnsureNotDisposed() + { + if (this.memoryOwners == null) + { + throw new ObjectDisposedException(nameof(UniformMemoryGroup)); + } + } + } + } +} diff --git a/src/ImageSharp/Memory/DiscontinuousProto/UniformMemoryGroup{T}.External.cs b/src/ImageSharp/Memory/DiscontinuousProto/UniformMemoryGroup{T}.External.cs new file mode 100644 index 000000000..024b42a3c --- /dev/null +++ b/src/ImageSharp/Memory/DiscontinuousProto/UniformMemoryGroup{T}.External.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; + +namespace SixLabors.ImageSharp.Memory.DiscontinuousProto +{ + public abstract partial class UniformMemoryGroup + { + private class External : UniformMemoryGroup + { + private readonly ReadOnlyMemory> source; + + public External(ReadOnlyMemory> source) + { + // TODO: sizes should be uniform, validate! + + this.source = source; + } + + public override IEnumerator> GetEnumerator() + { + for (int i = 0; i < this.source.Length; i++) + { + yield return this.source.Span[i]; + } + } + + public override int Count => this.source.Length; + + public override Memory this[int index] => this.source.Span[index]; + + public override void Dispose() + { + // No ownership nothing to dispose + } + } + } +} diff --git a/src/ImageSharp/Memory/DiscontinuousProto/UniformMemoryGroup{T}.cs b/src/ImageSharp/Memory/DiscontinuousProto/UniformMemoryGroup{T}.cs new file mode 100644 index 000000000..4682d6813 --- /dev/null +++ b/src/ImageSharp/Memory/DiscontinuousProto/UniformMemoryGroup{T}.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace SixLabors.ImageSharp.Memory.DiscontinuousProto +{ + /// + /// Represents a group of one or more uniformly-sized discontinuous memory segments, owned by this instance. + /// + /// The element type. + public abstract partial class UniformMemoryGroup : IUniformMemoryGroup, IDisposable where T : struct + { + public abstract IEnumerator> GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); + + public abstract int Count { get; } + + public abstract Memory this[int index] { get; } + + public abstract void Dispose(); + + public bool IsValid { get; protected set; } + + public static UniformMemoryGroup Allocate(MemoryAllocator allocator, long length) + { + long bufferCount = length / allocator.GetMaximumContiguousBufferLength(); + + // TODO: Allocate bufferCount buffers + throw new NotImplementedException(); + } + + public static UniformMemoryGroup Wrap(params Memory[] source) => Wrap(source.AsMemory()); + + public static UniformMemoryGroup Wrap(ReadOnlyMemory> source) + { + return new External(source); + } + } +} diff --git a/tests/ImageSharp.Tests/TestUtilities/TestMemoryAllocator.cs b/tests/ImageSharp.Tests/TestUtilities/TestMemoryAllocator.cs index fcda2eaa1..47bedda9a 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestMemoryAllocator.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestMemoryAllocator.cs @@ -24,6 +24,8 @@ namespace SixLabors.ImageSharp.Tests.Memory public IList AllocationLog => this.allocationLog; + protected internal override int GetMaximumContiguousBufferLength() => int.MaxValue; + public override IMemoryOwner Allocate(int length, AllocationOptions options = AllocationOptions.None) { T[] array = this.AllocateArray(length, options); @@ -152,4 +154,4 @@ namespace SixLabors.ImageSharp.Tests.Memory } } } -} \ No newline at end of file +} From 32502d041b96fde7bb316db73b90a6a8a49fc37a Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Tue, 28 Jan 2020 02:30:23 +0100 Subject: [PATCH 03/62] adjust names --- ...p{T}.External.cs => UniformMemoryGroup{T}.Consumed.cs} | 5 +++-- ...oup{T}.Allocated.cs => UniformMemoryGroup{T}.Owned.cs} | 5 +++-- .../Memory/DiscontinuousProto/UniformMemoryGroup{T}.cs | 8 +++++++- 3 files changed, 13 insertions(+), 5 deletions(-) rename src/ImageSharp/Memory/DiscontinuousProto/{UniformMemoryGroup{T}.External.cs => UniformMemoryGroup{T}.Consumed.cs} (83%) rename src/ImageSharp/Memory/DiscontinuousProto/{UniformMemoryGroup{T}.Allocated.cs => UniformMemoryGroup{T}.Owned.cs} (91%) diff --git a/src/ImageSharp/Memory/DiscontinuousProto/UniformMemoryGroup{T}.External.cs b/src/ImageSharp/Memory/DiscontinuousProto/UniformMemoryGroup{T}.Consumed.cs similarity index 83% rename from src/ImageSharp/Memory/DiscontinuousProto/UniformMemoryGroup{T}.External.cs rename to src/ImageSharp/Memory/DiscontinuousProto/UniformMemoryGroup{T}.Consumed.cs index 024b42a3c..79c2853b3 100644 --- a/src/ImageSharp/Memory/DiscontinuousProto/UniformMemoryGroup{T}.External.cs +++ b/src/ImageSharp/Memory/DiscontinuousProto/UniformMemoryGroup{T}.Consumed.cs @@ -5,11 +5,12 @@ namespace SixLabors.ImageSharp.Memory.DiscontinuousProto { public abstract partial class UniformMemoryGroup { - private class External : UniformMemoryGroup + // Analogous to the "consumed" variant of MemorySource + private class Consumed : UniformMemoryGroup { private readonly ReadOnlyMemory> source; - public External(ReadOnlyMemory> source) + public Consumed(ReadOnlyMemory> source) { // TODO: sizes should be uniform, validate! diff --git a/src/ImageSharp/Memory/DiscontinuousProto/UniformMemoryGroup{T}.Allocated.cs b/src/ImageSharp/Memory/DiscontinuousProto/UniformMemoryGroup{T}.Owned.cs similarity index 91% rename from src/ImageSharp/Memory/DiscontinuousProto/UniformMemoryGroup{T}.Allocated.cs rename to src/ImageSharp/Memory/DiscontinuousProto/UniformMemoryGroup{T}.Owned.cs index b9157b59d..7a5c75070 100644 --- a/src/ImageSharp/Memory/DiscontinuousProto/UniformMemoryGroup{T}.Allocated.cs +++ b/src/ImageSharp/Memory/DiscontinuousProto/UniformMemoryGroup{T}.Owned.cs @@ -5,13 +5,14 @@ using System.Linq; namespace SixLabors.ImageSharp.Memory.DiscontinuousProto { + // Analogous to the "owned" variant of MemorySource public abstract partial class UniformMemoryGroup { - private class Allocated : UniformMemoryGroup + private class Owned : UniformMemoryGroup { private IMemoryOwner[] memoryOwners; - public Allocated(IMemoryOwner[] memoryOwners) + public Owned(IMemoryOwner[] memoryOwners) { this.memoryOwners = memoryOwners; } diff --git a/src/ImageSharp/Memory/DiscontinuousProto/UniformMemoryGroup{T}.cs b/src/ImageSharp/Memory/DiscontinuousProto/UniformMemoryGroup{T}.cs index 4682d6813..465384f01 100644 --- a/src/ImageSharp/Memory/DiscontinuousProto/UniformMemoryGroup{T}.cs +++ b/src/ImageSharp/Memory/DiscontinuousProto/UniformMemoryGroup{T}.cs @@ -34,7 +34,13 @@ namespace SixLabors.ImageSharp.Memory.DiscontinuousProto public static UniformMemoryGroup Wrap(ReadOnlyMemory> source) { - return new External(source); + return new Consumed(source); + } + + // Analogous to current MemorySource.SwapOrCopyContent() + public static void SwapOrCopyContent(UniformMemoryGroup destination, UniformMemoryGroup source) + { + throw new NotImplementedException(); } } } From 1e663d72de8ce9bb6831b79965d179a06af7ebc9 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Tue, 28 Jan 2020 02:43:34 +0100 Subject: [PATCH 04/62] better comments --- .../Memory/DiscontinuousProto/IUniformMemoryGroup{T}.cs | 2 +- .../Memory/DiscontinuousProto/UniformMemoryGroup{T}.cs | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Memory/DiscontinuousProto/IUniformMemoryGroup{T}.cs b/src/ImageSharp/Memory/DiscontinuousProto/IUniformMemoryGroup{T}.cs index e6c71fa55..d0ec69bea 100644 --- a/src/ImageSharp/Memory/DiscontinuousProto/IUniformMemoryGroup{T}.cs +++ b/src/ImageSharp/Memory/DiscontinuousProto/IUniformMemoryGroup{T}.cs @@ -4,7 +4,7 @@ using System.Collections.Generic; namespace SixLabors.ImageSharp.Memory.DiscontinuousProto { /// - /// Represents a group of one or more uniformly-sized discontinuous memory segments. + /// Represents discontinuous group of multiple uniformly-sized memory segments. /// The last segment can be smaller than the preceding ones. /// /// The element type. diff --git a/src/ImageSharp/Memory/DiscontinuousProto/UniformMemoryGroup{T}.cs b/src/ImageSharp/Memory/DiscontinuousProto/UniformMemoryGroup{T}.cs index 465384f01..4d89fbf72 100644 --- a/src/ImageSharp/Memory/DiscontinuousProto/UniformMemoryGroup{T}.cs +++ b/src/ImageSharp/Memory/DiscontinuousProto/UniformMemoryGroup{T}.cs @@ -5,7 +5,9 @@ using System.Collections.Generic; namespace SixLabors.ImageSharp.Memory.DiscontinuousProto { /// - /// Represents a group of one or more uniformly-sized discontinuous memory segments, owned by this instance. + /// Represents discontinuous group of multiple uniformly-sized memory segments. + /// The underlying buffers may change with time, therefore it's not safe to expose them directly on + /// and . /// /// The element type. public abstract partial class UniformMemoryGroup : IUniformMemoryGroup, IDisposable where T : struct From 1b54376c1cf3ce966367027d6da63cdcb5dc8f6d Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Tue, 28 Jan 2020 03:12:18 +0100 Subject: [PATCH 05/62] bufferLengthAlignment --- .../Memory/DiscontinuousProto/UniformMemoryGroup{T}.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Memory/DiscontinuousProto/UniformMemoryGroup{T}.cs b/src/ImageSharp/Memory/DiscontinuousProto/UniformMemoryGroup{T}.cs index 4d89fbf72..974f68aac 100644 --- a/src/ImageSharp/Memory/DiscontinuousProto/UniformMemoryGroup{T}.cs +++ b/src/ImageSharp/Memory/DiscontinuousProto/UniformMemoryGroup{T}.cs @@ -24,11 +24,12 @@ namespace SixLabors.ImageSharp.Memory.DiscontinuousProto public bool IsValid { get; protected set; } - public static UniformMemoryGroup Allocate(MemoryAllocator allocator, long length) + // bufferLengthAlignment == image.Width in row-major images + public static UniformMemoryGroup Allocate(MemoryAllocator allocator, long length, int bufferLengthAlignment) { long bufferCount = length / allocator.GetMaximumContiguousBufferLength(); - // TODO: Allocate bufferCount buffers + // TODO: Adjust bufferCount to bufferLengthAlignment, and allocate bufferCount buffers throw new NotImplementedException(); } From ef9bc9ec27843f99f86b27f4adbd94496ce2f853 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Tue, 28 Jan 2020 03:14:58 +0100 Subject: [PATCH 06/62] bufferLengthAlignment --- .../Memory/DiscontinuousProto/UniformMemoryGroup{T}.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Memory/DiscontinuousProto/UniformMemoryGroup{T}.cs b/src/ImageSharp/Memory/DiscontinuousProto/UniformMemoryGroup{T}.cs index 974f68aac..2fe3adce8 100644 --- a/src/ImageSharp/Memory/DiscontinuousProto/UniformMemoryGroup{T}.cs +++ b/src/ImageSharp/Memory/DiscontinuousProto/UniformMemoryGroup{T}.cs @@ -29,7 +29,7 @@ namespace SixLabors.ImageSharp.Memory.DiscontinuousProto { long bufferCount = length / allocator.GetMaximumContiguousBufferLength(); - // TODO: Adjust bufferCount to bufferLengthAlignment, and allocate bufferCount buffers + // TODO: Adjust bufferCount, and calculate the uniform buffer length with respect to bufferLengthAlignment, and allocate bufferCount buffers throw new NotImplementedException(); } From 8764fd44f584def9658546817519dcc25cec35db Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Tue, 28 Jan 2020 03:17:37 +0100 Subject: [PATCH 07/62] make stuff internal --- .../Memory/DiscontinuousProto/UniformMemoryGroupView{T}.cs | 2 +- .../Memory/DiscontinuousProto/UniformMemoryGroup{T}.Consumed.cs | 2 +- .../Memory/DiscontinuousProto/UniformMemoryGroup{T}.Owned.cs | 2 +- .../Memory/DiscontinuousProto/UniformMemoryGroup{T}.cs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ImageSharp/Memory/DiscontinuousProto/UniformMemoryGroupView{T}.cs b/src/ImageSharp/Memory/DiscontinuousProto/UniformMemoryGroupView{T}.cs index 68ef5c25d..4e5b04dfd 100644 --- a/src/ImageSharp/Memory/DiscontinuousProto/UniformMemoryGroupView{T}.cs +++ b/src/ImageSharp/Memory/DiscontinuousProto/UniformMemoryGroupView{T}.cs @@ -15,7 +15,7 @@ namespace SixLabors.ImageSharp.Memory.DiscontinuousProto /// instance becomes invalid, throwing an exception on all operations. /// /// The element type. - public class UniformMemoryGroupView : IUniformMemoryGroup where T : struct + internal class UniformMemoryGroupView : IUniformMemoryGroup where T : struct { private readonly UniformMemoryGroup owner; private readonly MemoryOwnerWrapper[] memoryWrappers; diff --git a/src/ImageSharp/Memory/DiscontinuousProto/UniformMemoryGroup{T}.Consumed.cs b/src/ImageSharp/Memory/DiscontinuousProto/UniformMemoryGroup{T}.Consumed.cs index 79c2853b3..17410e900 100644 --- a/src/ImageSharp/Memory/DiscontinuousProto/UniformMemoryGroup{T}.Consumed.cs +++ b/src/ImageSharp/Memory/DiscontinuousProto/UniformMemoryGroup{T}.Consumed.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; namespace SixLabors.ImageSharp.Memory.DiscontinuousProto { - public abstract partial class UniformMemoryGroup + internal abstract partial class UniformMemoryGroup { // Analogous to the "consumed" variant of MemorySource private class Consumed : UniformMemoryGroup diff --git a/src/ImageSharp/Memory/DiscontinuousProto/UniformMemoryGroup{T}.Owned.cs b/src/ImageSharp/Memory/DiscontinuousProto/UniformMemoryGroup{T}.Owned.cs index 7a5c75070..d02975cbc 100644 --- a/src/ImageSharp/Memory/DiscontinuousProto/UniformMemoryGroup{T}.Owned.cs +++ b/src/ImageSharp/Memory/DiscontinuousProto/UniformMemoryGroup{T}.Owned.cs @@ -6,7 +6,7 @@ using System.Linq; namespace SixLabors.ImageSharp.Memory.DiscontinuousProto { // Analogous to the "owned" variant of MemorySource - public abstract partial class UniformMemoryGroup + internal abstract partial class UniformMemoryGroup { private class Owned : UniformMemoryGroup { diff --git a/src/ImageSharp/Memory/DiscontinuousProto/UniformMemoryGroup{T}.cs b/src/ImageSharp/Memory/DiscontinuousProto/UniformMemoryGroup{T}.cs index 2fe3adce8..794239377 100644 --- a/src/ImageSharp/Memory/DiscontinuousProto/UniformMemoryGroup{T}.cs +++ b/src/ImageSharp/Memory/DiscontinuousProto/UniformMemoryGroup{T}.cs @@ -10,7 +10,7 @@ namespace SixLabors.ImageSharp.Memory.DiscontinuousProto /// and . /// /// The element type. - public abstract partial class UniformMemoryGroup : IUniformMemoryGroup, IDisposable where T : struct + internal abstract partial class UniformMemoryGroup : IUniformMemoryGroup, IDisposable where T : struct { public abstract IEnumerator> GetEnumerator(); From 591ed2765d0116acd19ad69c0e28ab33b388c811 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 2 Feb 2020 19:23:30 +0100 Subject: [PATCH 08/62] test cases for MemoryGroup.Allocate() --- src/ImageSharp/ImageSharp.csproj.DotSettings | 2 + .../Allocators/ArrayPoolMemoryAllocator.cs | 2 +- .../Memory/Allocators/MemoryAllocator.cs | 5 +- .../Allocators/SimpleGcMemoryAllocator.cs | 2 +- .../DiscontiguousBuffers/IMemoryGroup{T}.cs | 21 ++++ .../InvalidMemoryOperationException.cs | 2 +- .../MemoryGroupView{T}.cs} | 18 ++-- .../MemoryGroup{T}.Consumed.cs} | 6 +- .../MemoryGroup{T}.Owned.cs} | 8 +- .../MemoryGroup{T}.cs} | 16 ++-- .../IUniformMemoryGroup{T}.cs | 15 --- .../DiscontiguousBuffers/MemoryGroupTests.cs | 96 +++++++++++++++++++ .../TestUtilities/TestMemoryAllocator.cs | 4 +- 13 files changed, 154 insertions(+), 43 deletions(-) create mode 100644 src/ImageSharp/Memory/DiscontiguousBuffers/IMemoryGroup{T}.cs rename src/ImageSharp/Memory/{DiscontinuousProto => DiscontiguousBuffers}/InvalidMemoryOperationException.cs (65%) rename src/ImageSharp/Memory/{DiscontinuousProto/UniformMemoryGroupView{T}.cs => DiscontiguousBuffers/MemoryGroupView{T}.cs} (77%) rename src/ImageSharp/Memory/{DiscontinuousProto/UniformMemoryGroup{T}.Consumed.cs => DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs} (84%) rename src/ImageSharp/Memory/{DiscontinuousProto/UniformMemoryGroup{T}.Owned.cs => DiscontiguousBuffers/MemoryGroup{T}.Owned.cs} (86%) rename src/ImageSharp/Memory/{DiscontinuousProto/UniformMemoryGroup{T}.cs => DiscontiguousBuffers/MemoryGroup{T}.cs} (65%) delete mode 100644 src/ImageSharp/Memory/DiscontinuousProto/IUniformMemoryGroup{T}.cs create mode 100644 tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs diff --git a/src/ImageSharp/ImageSharp.csproj.DotSettings b/src/ImageSharp/ImageSharp.csproj.DotSettings index 018ca75cd..6896e069c 100644 --- a/src/ImageSharp/ImageSharp.csproj.DotSettings +++ b/src/ImageSharp/ImageSharp.csproj.DotSettings @@ -2,6 +2,8 @@ True True True + True + True True True True diff --git a/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.cs index 57a5b77bc..e53929c5c 100644 --- a/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.cs +++ b/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.cs @@ -95,7 +95,7 @@ namespace SixLabors.ImageSharp.Memory public int MaximumContiguousBufferLength { get; set; } = Int32.MaxValue; /// - protected internal override int GetMaximumContiguousBufferLength() => this.MaximumContiguousBufferLength; + protected internal override int GetBlockCapacity() => this.MaximumContiguousBufferLength; /// public override IMemoryOwner Allocate(int length, AllocationOptions options = AllocationOptions.None) diff --git a/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs index 1e1f69784..ccb5bf2e8 100644 --- a/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs +++ b/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs @@ -11,9 +11,10 @@ namespace SixLabors.ImageSharp.Memory public abstract class MemoryAllocator { /// - /// Gets the length of the largest contiguous buffer that can be handled by this allocator instance. + /// Gets the length of the largest contiguous buffer that can be handled by this allocator instance in bytes. /// - protected internal abstract int GetMaximumContiguousBufferLength(); + /// The length of the largest contiguous buffer that can be handled by this allocator instance. + protected internal abstract int GetBlockCapacity(); /// /// Allocates an , holding a of length . diff --git a/src/ImageSharp/Memory/Allocators/SimpleGcMemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/SimpleGcMemoryAllocator.cs index ee9afbac5..88830c551 100644 --- a/src/ImageSharp/Memory/Allocators/SimpleGcMemoryAllocator.cs +++ b/src/ImageSharp/Memory/Allocators/SimpleGcMemoryAllocator.cs @@ -12,7 +12,7 @@ namespace SixLabors.ImageSharp.Memory public sealed class SimpleGcMemoryAllocator : MemoryAllocator { /// - protected internal override int GetMaximumContiguousBufferLength() => int.MaxValue; + protected internal override int GetBlockCapacity() => int.MaxValue; /// public override IMemoryOwner Allocate(int length, AllocationOptions options = AllocationOptions.None) diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/IMemoryGroup{T}.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/IMemoryGroup{T}.cs new file mode 100644 index 000000000..eaacef713 --- /dev/null +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/IMemoryGroup{T}.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; + +namespace SixLabors.ImageSharp.Memory +{ + /// + /// Represents discontiguous group of multiple uniformly-sized memory segments. + /// The last segment can be smaller than the preceding ones. + /// + /// The element type. + public interface IMemoryGroup : IReadOnlyList> + where T : struct + { + /// + /// Gets the number of elements per contiguous sub-block. + /// + public int BlockSize { get; } + + bool IsValid { get; } + } +} diff --git a/src/ImageSharp/Memory/DiscontinuousProto/InvalidMemoryOperationException.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/InvalidMemoryOperationException.cs similarity index 65% rename from src/ImageSharp/Memory/DiscontinuousProto/InvalidMemoryOperationException.cs rename to src/ImageSharp/Memory/DiscontiguousBuffers/InvalidMemoryOperationException.cs index f756a1246..b211a13f3 100644 --- a/src/ImageSharp/Memory/DiscontinuousProto/InvalidMemoryOperationException.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/InvalidMemoryOperationException.cs @@ -1,6 +1,6 @@ using System; -namespace SixLabors.ImageSharp.Memory.DiscontinuousProto +namespace SixLabors.ImageSharp.Memory { public class InvalidMemoryOperationException : InvalidOperationException { diff --git a/src/ImageSharp/Memory/DiscontinuousProto/UniformMemoryGroupView{T}.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupView{T}.cs similarity index 77% rename from src/ImageSharp/Memory/DiscontinuousProto/UniformMemoryGroupView{T}.cs rename to src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupView{T}.cs index 4e5b04dfd..a2d5c0579 100644 --- a/src/ImageSharp/Memory/DiscontinuousProto/UniformMemoryGroupView{T}.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupView{T}.cs @@ -3,24 +3,24 @@ using System.Buffers; using System.Collections; using System.Collections.Generic; -namespace SixLabors.ImageSharp.Memory.DiscontinuousProto +namespace SixLabors.ImageSharp.Memory { /// - /// Implements , defining a view for + /// Implements , defining a view for /// rather than owning the segments. /// /// /// This type provides an indirection, protecting the users of publicly exposed memory API-s - /// from internal memory-swaps. Whenever an internal swap happens, the + /// from internal memory-swaps. Whenever an internal swap happens, the /// instance becomes invalid, throwing an exception on all operations. /// /// The element type. - internal class UniformMemoryGroupView : IUniformMemoryGroup where T : struct + internal class MemoryGroupView : IMemoryGroup where T : struct { - private readonly UniformMemoryGroup owner; + private readonly Memory.MemoryGroup owner; private readonly MemoryOwnerWrapper[] memoryWrappers; - public UniformMemoryGroupView(UniformMemoryGroup owner) + public MemoryGroupView(Memory.MemoryGroup owner) { this.IsValid = true; this.owner = owner; @@ -40,15 +40,17 @@ namespace SixLabors.ImageSharp.Memory.DiscontinuousProto public Memory this[int index] => throw new NotImplementedException(); + public int BlockSize => this.owner.BlockSize; + public bool IsValid { get; internal set; } class MemoryOwnerWrapper : MemoryManager { - private UniformMemoryGroupView view; + private MemoryGroupView view; private int index; - public MemoryOwnerWrapper(UniformMemoryGroupView view, int index) + public MemoryOwnerWrapper(MemoryGroupView view, int index) { this.view = view; this.index = index; diff --git a/src/ImageSharp/Memory/DiscontinuousProto/UniformMemoryGroup{T}.Consumed.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs similarity index 84% rename from src/ImageSharp/Memory/DiscontinuousProto/UniformMemoryGroup{T}.Consumed.cs rename to src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs index 17410e900..03f61b75b 100644 --- a/src/ImageSharp/Memory/DiscontinuousProto/UniformMemoryGroup{T}.Consumed.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs @@ -1,12 +1,12 @@ using System; using System.Collections.Generic; -namespace SixLabors.ImageSharp.Memory.DiscontinuousProto +namespace SixLabors.ImageSharp.Memory { - internal abstract partial class UniformMemoryGroup + internal abstract partial class MemoryGroup { // Analogous to the "consumed" variant of MemorySource - private class Consumed : UniformMemoryGroup + private class Consumed : MemoryGroup { private readonly ReadOnlyMemory> source; diff --git a/src/ImageSharp/Memory/DiscontinuousProto/UniformMemoryGroup{T}.Owned.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs similarity index 86% rename from src/ImageSharp/Memory/DiscontinuousProto/UniformMemoryGroup{T}.Owned.cs rename to src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs index d02975cbc..b15d7d676 100644 --- a/src/ImageSharp/Memory/DiscontinuousProto/UniformMemoryGroup{T}.Owned.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs @@ -3,12 +3,12 @@ using System.Buffers; using System.Collections.Generic; using System.Linq; -namespace SixLabors.ImageSharp.Memory.DiscontinuousProto +namespace SixLabors.ImageSharp.Memory { // Analogous to the "owned" variant of MemorySource - internal abstract partial class UniformMemoryGroup + internal abstract partial class MemoryGroup { - private class Owned : UniformMemoryGroup + private class Owned : MemoryGroup { private IMemoryOwner[] memoryOwners; @@ -62,7 +62,7 @@ namespace SixLabors.ImageSharp.Memory.DiscontinuousProto { if (this.memoryOwners == null) { - throw new ObjectDisposedException(nameof(UniformMemoryGroup)); + throw new ObjectDisposedException(nameof(MemoryGroup)); } } } diff --git a/src/ImageSharp/Memory/DiscontinuousProto/UniformMemoryGroup{T}.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs similarity index 65% rename from src/ImageSharp/Memory/DiscontinuousProto/UniformMemoryGroup{T}.cs rename to src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs index 794239377..670a0aaf6 100644 --- a/src/ImageSharp/Memory/DiscontinuousProto/UniformMemoryGroup{T}.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs @@ -2,7 +2,7 @@ using System; using System.Collections; using System.Collections.Generic; -namespace SixLabors.ImageSharp.Memory.DiscontinuousProto +namespace SixLabors.ImageSharp.Memory { /// /// Represents discontinuous group of multiple uniformly-sized memory segments. @@ -10,7 +10,7 @@ namespace SixLabors.ImageSharp.Memory.DiscontinuousProto /// and . /// /// The element type. - internal abstract partial class UniformMemoryGroup : IUniformMemoryGroup, IDisposable where T : struct + internal abstract partial class MemoryGroup : IMemoryGroup, IDisposable where T : struct { public abstract IEnumerator> GetEnumerator(); @@ -22,26 +22,28 @@ namespace SixLabors.ImageSharp.Memory.DiscontinuousProto public abstract void Dispose(); + public int BlockSize { get; } + public bool IsValid { get; protected set; } // bufferLengthAlignment == image.Width in row-major images - public static UniformMemoryGroup Allocate(MemoryAllocator allocator, long length, int bufferLengthAlignment) + public static MemoryGroup Allocate(MemoryAllocator allocator, long totalLength, int blockAlignment) { - long bufferCount = length / allocator.GetMaximumContiguousBufferLength(); + long bufferCount = totalLength / allocator.GetBlockCapacity(); // TODO: Adjust bufferCount, and calculate the uniform buffer length with respect to bufferLengthAlignment, and allocate bufferCount buffers throw new NotImplementedException(); } - public static UniformMemoryGroup Wrap(params Memory[] source) => Wrap(source.AsMemory()); + public static MemoryGroup Wrap(params Memory[] source) => Wrap(source.AsMemory()); - public static UniformMemoryGroup Wrap(ReadOnlyMemory> source) + public static MemoryGroup Wrap(ReadOnlyMemory> source) { return new Consumed(source); } // Analogous to current MemorySource.SwapOrCopyContent() - public static void SwapOrCopyContent(UniformMemoryGroup destination, UniformMemoryGroup source) + public static void SwapOrCopyContent(MemoryGroup destination, MemoryGroup source) { throw new NotImplementedException(); } diff --git a/src/ImageSharp/Memory/DiscontinuousProto/IUniformMemoryGroup{T}.cs b/src/ImageSharp/Memory/DiscontinuousProto/IUniformMemoryGroup{T}.cs deleted file mode 100644 index d0ec69bea..000000000 --- a/src/ImageSharp/Memory/DiscontinuousProto/IUniformMemoryGroup{T}.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace SixLabors.ImageSharp.Memory.DiscontinuousProto -{ - /// - /// Represents discontinuous group of multiple uniformly-sized memory segments. - /// The last segment can be smaller than the preceding ones. - /// - /// The element type. - public interface IUniformMemoryGroup : IReadOnlyList> where T : struct - { - bool IsValid { get; } - } -} diff --git a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs new file mode 100644 index 000000000..adb398ee6 --- /dev/null +++ b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs @@ -0,0 +1,96 @@ +using System.Linq; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Memory; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers +{ + public class MemoryGroupTests + { + public class Allocate + { + private readonly TestMemoryAllocator memoryAllocator = new TestMemoryAllocator(); + +#pragma warning disable SA1509 + public static TheoryData AllocateData = + new TheoryData() + { + { default(S5), 22, 4, 4, 1, 4, 4 }, + { default(S5), 22, 4, 7, 2, 4, 3 }, + { default(S5), 22, 4, 8, 2, 4, 4 }, + { default(S5), 22, 4, 21, 5, 4, 1 }, + { default(S5), 22, 4, 0, 0, -1, -1 }, + + { default(S4), 50, 12, 12, 1, 12, 12 }, + { default(S4), 50, 7, 12, 2, 7, 5 }, + { default(S4), 50, 6, 12, 2, 6, 6 }, + { default(S4), 50, 5, 12, 2, 10, 2 }, + { default(S4), 50, 4, 12, 1, 12, 12 }, + { default(S4), 50, 3, 12, 1, 12, 12 }, + { default(S4), 50, 2, 12, 1, 12, 12 }, + { default(S4), 50, 1, 12, 1, 12, 12 }, + + { default(S4), 50, 12, 13, 2, 12, 1 }, + { default(S4), 50, 7, 21, 3, 7, 7 }, + { default(S4), 50, 7, 23, 3, 7, 2 }, + + { default(byte), 1000, 512, 2047, 4, 512, 511 } + }; + + [Theory] + [MemberData(nameof(AllocateData))] + public void CreatesBlocksOfCorrectSizes( + T dummy, + int blockCapacity, + int blockAlignment, + long totalLength, + int expectedNumberOfBlocks, + int expectedBlockSize, + int expectedSizeOfLastBlock) + where T : struct + { + this.memoryAllocator.BlockCapacity = blockCapacity; + + // Act: + using var g = MemoryGroup.Allocate(this.memoryAllocator, totalLength, blockAlignment); + + // Assert: + Assert.Equal(expectedNumberOfBlocks, g.Count); + Assert.Equal(expectedBlockSize, g.BlockSize); + if (g.Count == 0) + { + return; + } + + for (int i = 0; i < g.Count - 1; i++) + { + Assert.Equal(g[i].Length, expectedBlockSize); + } + + Assert.Equal(g.Last().Length, expectedSizeOfLastBlock); + } + + [Fact] + public void WhenBlockAlignmentIsOverCapacity_Throws_InvalidMemoryOperationException() + { + this.memoryAllocator.BlockCapacity = 42; + + Assert.Throws(() => + { + MemoryGroup.Allocate(this.memoryAllocator, 50, 43); + }); + } + } + + + [StructLayout(LayoutKind.Sequential, Size = 5)] + private struct S5 + { + } + + [StructLayout(LayoutKind.Sequential, Size = 4)] + private struct S4 + { + } + } +} diff --git a/tests/ImageSharp.Tests/TestUtilities/TestMemoryAllocator.cs b/tests/ImageSharp.Tests/TestUtilities/TestMemoryAllocator.cs index b6b297fab..6f9473116 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestMemoryAllocator.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestMemoryAllocator.cs @@ -25,9 +25,11 @@ namespace SixLabors.ImageSharp.Tests.Memory /// public byte DirtyValue { get; } + public int BlockCapacity { get; set; } = int.MaxValue; + public IList AllocationLog => this.allocationLog; - protected internal override int GetMaximumContiguousBufferLength() => int.MaxValue; + protected internal override int GetBlockCapacity() => this.BlockCapacity; public override IMemoryOwner Allocate(int length, AllocationOptions options = AllocationOptions.None) { From d3e3e953ba143344c3475061aecb5f4f34c1f7ff Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 2 Feb 2020 19:50:35 +0100 Subject: [PATCH 09/62] More tests for MemoryGroup.Allocate() --- .../DiscontiguousBuffers/MemoryGroup{T}.cs | 5 +- .../DiscontiguousBuffers/MemoryGroupTests.cs | 33 ++++++++- .../TestUtilities/TestMemoryAllocator.cs | 67 ++++++++++++------- 3 files changed, 79 insertions(+), 26 deletions(-) diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs index 670a0aaf6..e9c0be02d 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs @@ -27,7 +27,10 @@ namespace SixLabors.ImageSharp.Memory public bool IsValid { get; protected set; } // bufferLengthAlignment == image.Width in row-major images - public static MemoryGroup Allocate(MemoryAllocator allocator, long totalLength, int blockAlignment) + public static MemoryGroup Allocate(MemoryAllocator allocator, + long totalLength, + int blockAlignment, + AllocationOptions allocationOptions = AllocationOptions.None) { long bufferCount = totalLength / allocator.GetBlockCapacity(); diff --git a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs index adb398ee6..85aebb874 100644 --- a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs +++ b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs @@ -1,4 +1,5 @@ -using System.Linq; +using System.Collections.Generic; +using System.Linq; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Memory; using Xunit; @@ -80,6 +81,36 @@ namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers MemoryGroup.Allocate(this.memoryAllocator, 50, 43); }); } + + [Theory] + [InlineData(AllocationOptions.None)] + [InlineData(AllocationOptions.Clean)] + public void MemoryAllocator_IsUtilizedCorrectly(AllocationOptions allocationOptions) + { + this.memoryAllocator.BlockCapacity = 200; + + HashSet bufferHashes; + + int expectedBlockCount = 5; + using (var g = MemoryGroup.Allocate(this.memoryAllocator, 500, 100, allocationOptions)) + { + IReadOnlyList allocationLog = this.memoryAllocator.AllocationLog; + Assert.Equal(expectedBlockCount, allocationLog.Count); + bufferHashes = allocationLog.Select(l => l.HashCodeOfBuffer).ToHashSet(); + Assert.Equal(expectedBlockCount, bufferHashes.Count); + Assert.Equal(0, this.memoryAllocator.ReturnLog.Count); + + for (int i = 0; i < expectedBlockCount; i++) + { + Assert.Equal(allocationOptions, allocationLog[i].AllocationOptions); + Assert.Equal(100, allocationLog[i].Length); + Assert.Equal(200, allocationLog[i].LengthInBytes); + } + } + + Assert.Equal(expectedBlockCount, this.memoryAllocator.ReturnLog.Count); + Assert.True(bufferHashes.SetEquals(this.memoryAllocator.ReturnLog.Select(l => l.HashCodeOfBuffer))); + } } diff --git a/tests/ImageSharp.Tests/TestUtilities/TestMemoryAllocator.cs b/tests/ImageSharp.Tests/TestUtilities/TestMemoryAllocator.cs index 6f9473116..c49a68990 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestMemoryAllocator.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestMemoryAllocator.cs @@ -13,7 +13,8 @@ namespace SixLabors.ImageSharp.Tests.Memory { internal class TestMemoryAllocator : MemoryAllocator { - private List allocationLog = new List(); + private readonly List allocationLog = new List(); + private readonly List returnLog = new List(); public TestMemoryAllocator(byte dirtyValue = 42) { @@ -27,27 +28,29 @@ namespace SixLabors.ImageSharp.Tests.Memory public int BlockCapacity { get; set; } = int.MaxValue; - public IList AllocationLog => this.allocationLog; + public IReadOnlyList AllocationLog => this.allocationLog; + + public IReadOnlyList ReturnLog => this.returnLog; protected internal override int GetBlockCapacity() => this.BlockCapacity; public override IMemoryOwner Allocate(int length, AllocationOptions options = AllocationOptions.None) { T[] array = this.AllocateArray(length, options); - return new BasicArrayBuffer(array, length); + 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); + return new ManagedByteBuffer(array, this); } private T[] AllocateArray(int length, AllocationOptions options) where T : struct { - this.allocationLog.Add(AllocationRequest.Create(options, length)); var array = new T[length + 42]; + this.allocationLog.Add(AllocationRequest.Create(options, length, array)); if (options == AllocationOptions.None) { @@ -58,25 +61,32 @@ namespace SixLabors.ImageSharp.Tests.Memory return array; } + private void Return(BasicArrayBuffer buffer) + where T : struct + { + this.returnLog.Add(new ReturnRequest(buffer.Array.GetHashCode())); + } + public struct AllocationRequest { - private AllocationRequest(Type elementType, AllocationOptions allocationOptions, int length, int lengthInBytes) + private AllocationRequest(Type elementType, AllocationOptions allocationOptions, int length, int lengthInBytes, int hashCodeOfBuffer) { this.ElementType = elementType; this.AllocationOptions = allocationOptions; this.Length = length; this.LengthInBytes = lengthInBytes; + this.HashCodeOfBuffer = hashCodeOfBuffer; if (elementType == typeof(Vector4)) { } } - public static AllocationRequest Create(AllocationOptions allocationOptions, int length) + public static AllocationRequest Create(AllocationOptions allocationOptions, int length, T[] buffer) { Type type = typeof(T); int elementSize = Marshal.SizeOf(type); - return new AllocationRequest(type, allocationOptions, length, length * elementSize); + return new AllocationRequest(type, allocationOptions, length, length * elementSize, buffer.GetHashCode()); } public Type ElementType { get; } @@ -86,6 +96,18 @@ namespace SixLabors.ImageSharp.Tests.Memory public int Length { get; } public int LengthInBytes { get; } + + public int HashCodeOfBuffer { get; } + } + + public struct ReturnRequest + { + public ReturnRequest(int hashCodeOfBuffer) + { + this.HashCodeOfBuffer = hashCodeOfBuffer; + } + + public int HashCodeOfBuffer { get; } } /// @@ -94,36 +116,29 @@ namespace SixLabors.ImageSharp.Tests.Memory private class BasicArrayBuffer : MemoryManager where T : struct { + private readonly TestMemoryAllocator allocator; private GCHandle pinHandle; - /// - /// Initializes a new instance of the class - /// - /// The array - /// The length of the buffer - public BasicArrayBuffer(T[] array, int length) + public BasicArrayBuffer(T[] array, int length, TestMemoryAllocator allocator) { + this.allocator = allocator; DebugGuard.MustBeLessThanOrEqualTo(length, array.Length, nameof(length)); this.Array = array; this.Length = length; } - /// - /// Initializes a new instance of the class - /// - /// The array - public BasicArrayBuffer(T[] array) - : this(array, array.Length) + public BasicArrayBuffer(T[] array, TestMemoryAllocator allocator) + : this(array, array.Length, allocator) { } /// - /// Gets the array + /// Gets the array. /// public T[] Array { get; } /// - /// Gets the length + /// Gets the length. /// public int Length { get; } @@ -149,13 +164,17 @@ namespace SixLabors.ImageSharp.Tests.Memory /// protected override void Dispose(bool disposing) { + if (disposing) + { + this.allocator.Return(this); + } } } private class ManagedByteBuffer : BasicArrayBuffer, IManagedByteBuffer { - public ManagedByteBuffer(byte[] array) - : base(array) + public ManagedByteBuffer(byte[] array, TestMemoryAllocator allocator) + : base(array, allocator) { } } From b74e1b7695b6bb6cf886d2f504eee684d6cda8dc Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 2 Feb 2020 21:26:16 +0100 Subject: [PATCH 10/62] Allocate works --- .../Allocators/ArrayPoolMemoryAllocator.cs | 2 +- .../Memory/Allocators/MemoryAllocator.cs | 2 +- .../Allocators/SimpleGcMemoryAllocator.cs | 2 +- .../DiscontiguousBuffers/IMemoryGroup{T}.cs | 2 +- .../InvalidMemoryOperationException.cs | 18 +++++ .../MemoryGroupView{T}.cs | 2 +- .../MemoryGroup{T}.Consumed.cs | 13 ++- .../MemoryGroup{T}.Owned.cs | 3 +- .../DiscontiguousBuffers/MemoryGroup{T}.cs | 79 ++++++++++++++++--- .../DiscontiguousBuffers/MemoryGroupTests.cs | 64 ++++++++++----- .../TestUtilities/TestMemoryAllocator.cs | 4 +- 11 files changed, 146 insertions(+), 45 deletions(-) diff --git a/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.cs index e53929c5c..d341e93af 100644 --- a/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.cs +++ b/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.cs @@ -95,7 +95,7 @@ namespace SixLabors.ImageSharp.Memory public int MaximumContiguousBufferLength { get; set; } = Int32.MaxValue; /// - protected internal override int GetBlockCapacity() => this.MaximumContiguousBufferLength; + protected internal override int GetBufferCapacity() => this.MaximumContiguousBufferLength; /// public override IMemoryOwner Allocate(int length, AllocationOptions options = AllocationOptions.None) diff --git a/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs index ccb5bf2e8..9ed322c9c 100644 --- a/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs +++ b/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs @@ -14,7 +14,7 @@ namespace SixLabors.ImageSharp.Memory /// Gets the length of the largest contiguous buffer that can be handled by this allocator instance in bytes. /// /// The length of the largest contiguous buffer that can be handled by this allocator instance. - protected internal abstract int GetBlockCapacity(); + protected internal abstract int GetBufferCapacity(); /// /// Allocates an , holding a of length . diff --git a/src/ImageSharp/Memory/Allocators/SimpleGcMemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/SimpleGcMemoryAllocator.cs index 88830c551..293e807ef 100644 --- a/src/ImageSharp/Memory/Allocators/SimpleGcMemoryAllocator.cs +++ b/src/ImageSharp/Memory/Allocators/SimpleGcMemoryAllocator.cs @@ -12,7 +12,7 @@ namespace SixLabors.ImageSharp.Memory public sealed class SimpleGcMemoryAllocator : MemoryAllocator { /// - protected internal override int GetBlockCapacity() => int.MaxValue; + protected internal override int GetBufferCapacity() => int.MaxValue; /// public override IMemoryOwner Allocate(int length, AllocationOptions options = AllocationOptions.None) diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/IMemoryGroup{T}.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/IMemoryGroup{T}.cs index eaacef713..a2eafb160 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/IMemoryGroup{T}.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/IMemoryGroup{T}.cs @@ -14,7 +14,7 @@ namespace SixLabors.ImageSharp.Memory /// /// Gets the number of elements per contiguous sub-block. /// - public int BlockSize { get; } + public int BufferSize { get; } bool IsValid { get; } } diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/InvalidMemoryOperationException.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/InvalidMemoryOperationException.cs index b211a13f3..df30c2ee2 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/InvalidMemoryOperationException.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/InvalidMemoryOperationException.cs @@ -2,7 +2,25 @@ using System; namespace SixLabors.ImageSharp.Memory { + /// + /// Exception thrown on invalid memory (allocation) requests. + /// public class InvalidMemoryOperationException : InvalidOperationException { + /// + /// Initializes a new instance of the class. + /// + /// The exception message text. + public InvalidMemoryOperationException(string message) + : base(message) + { + } + + /// + /// Initializes a new instance of the class. + /// + public InvalidMemoryOperationException() + { + } } } diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupView{T}.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupView{T}.cs index a2d5c0579..b1077e254 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupView{T}.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupView{T}.cs @@ -40,7 +40,7 @@ namespace SixLabors.ImageSharp.Memory public Memory this[int index] => throw new NotImplementedException(); - public int BlockSize => this.owner.BlockSize; + public int BufferSize => this.owner.BufferSize; public bool IsValid { get; internal set; } diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs index 03f61b75b..20afb2d57 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs @@ -10,13 +10,16 @@ namespace SixLabors.ImageSharp.Memory { private readonly ReadOnlyMemory> source; - public Consumed(ReadOnlyMemory> source) + public Consumed(ReadOnlyMemory> source, int bufferSize) + : base(bufferSize) { - // TODO: sizes should be uniform, validate! - this.source = source; } + public override int Count => this.source.Length; + + public override Memory this[int index] => this.source.Span[index]; + public override IEnumerator> GetEnumerator() { for (int i = 0; i < this.source.Length; i++) @@ -25,10 +28,6 @@ namespace SixLabors.ImageSharp.Memory } } - public override int Count => this.source.Length; - - public override Memory this[int index] => this.source.Span[index]; - public override void Dispose() { // No ownership nothing to dispose diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs index b15d7d676..c90a24376 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs @@ -12,7 +12,8 @@ namespace SixLabors.ImageSharp.Memory { private IMemoryOwner[] memoryOwners; - public Owned(IMemoryOwner[] memoryOwners) + public Owned(IMemoryOwner[] memoryOwners, int bufferSize) + : base(bufferSize) { this.memoryOwners = memoryOwners; } diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs index e9c0be02d..9d36cc8dc 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs @@ -1,6 +1,8 @@ using System; +using System.Buffers; using System.Collections; using System.Collections.Generic; +using System.Runtime.CompilerServices; namespace SixLabors.ImageSharp.Memory { @@ -10,39 +12,96 @@ namespace SixLabors.ImageSharp.Memory /// and . /// /// The element type. - internal abstract partial class MemoryGroup : IMemoryGroup, IDisposable where T : struct + internal abstract partial class MemoryGroup : IMemoryGroup, IDisposable + where T : struct { - public abstract IEnumerator> GetEnumerator(); + private static readonly int ElementSize = Unsafe.SizeOf(); - IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); + private MemoryGroup(int bufferSize) => this.BufferSize = bufferSize; public abstract int Count { get; } + public int BufferSize { get; } + + public bool IsValid { get; private set; } = true; + public abstract Memory this[int index] { get; } public abstract void Dispose(); - public int BlockSize { get; } + public abstract IEnumerator> GetEnumerator(); - public bool IsValid { get; protected set; } + IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); // bufferLengthAlignment == image.Width in row-major images - public static MemoryGroup Allocate(MemoryAllocator allocator, + public static MemoryGroup Allocate( + MemoryAllocator allocator, long totalLength, int blockAlignment, AllocationOptions allocationOptions = AllocationOptions.None) { - long bufferCount = totalLength / allocator.GetBlockCapacity(); + Guard.NotNull(allocator, nameof(allocator)); + Guard.MustBeGreaterThanOrEqualTo(totalLength, 0, nameof(totalLength)); + Guard.MustBeGreaterThan(blockAlignment, 0, nameof(blockAlignment)); - // TODO: Adjust bufferCount, and calculate the uniform buffer length with respect to bufferLengthAlignment, and allocate bufferCount buffers - throw new NotImplementedException(); + int blockCapacityInElements = allocator.GetBufferCapacity() / ElementSize; + if (blockAlignment > blockCapacityInElements) + { + throw new InvalidMemoryOperationException(); + } + + int numberOfAlignedSegments = blockCapacityInElements / blockAlignment; + int bufferSize = numberOfAlignedSegments * blockAlignment; + if (totalLength > 0 && totalLength < bufferSize) + { + bufferSize = (int)totalLength; + } + + int sizeOfLastBuffer = (int)(totalLength % bufferSize); + long bufferCount = totalLength / bufferSize; + + if (sizeOfLastBuffer == 0) + { + sizeOfLastBuffer = bufferSize; + } + else + { + bufferCount++; + } + + var buffers = new IMemoryOwner[bufferCount]; + for (int i = 0; i < buffers.Length - 1; i++) + { + buffers[i] = allocator.Allocate(bufferSize, allocationOptions); + } + + if (bufferCount > 0) + { + buffers[^1] = allocator.Allocate(sizeOfLastBuffer, allocationOptions); + } + + return new Owned(buffers, bufferSize); } public static MemoryGroup Wrap(params Memory[] source) => Wrap(source.AsMemory()); public static MemoryGroup Wrap(ReadOnlyMemory> source) { - return new Consumed(source); + int bufferSize = source.Length > 0 ? source.Span[0].Length : 0; + for (int i = 1; i < source.Length - 1; i++) + { + if (source.Span[i].Length != bufferSize) + { + throw new InvalidMemoryOperationException("Wrap: buffers should be uniformly sized!"); + } + } + + if (source.Length > 0 && source.Span[^1].Length > bufferSize) + { + throw new InvalidMemoryOperationException("Wrap: the last buffer is too large!"); + } + + return new Consumed(source, bufferSize); } // Analogous to current MemorySource.SwapOrCopyContent() diff --git a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs index 85aebb874..c12c09b7a 100644 --- a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs +++ b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs @@ -3,11 +3,14 @@ using System.Linq; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Memory; using Xunit; +using Xunit.Sdk; namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers { public class MemoryGroupTests { + private readonly TestMemoryAllocator memoryAllocator = new TestMemoryAllocator(); + public class Allocate { private readonly TestMemoryAllocator memoryAllocator = new TestMemoryAllocator(); @@ -19,12 +22,12 @@ namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers { default(S5), 22, 4, 4, 1, 4, 4 }, { default(S5), 22, 4, 7, 2, 4, 3 }, { default(S5), 22, 4, 8, 2, 4, 4 }, - { default(S5), 22, 4, 21, 5, 4, 1 }, - { default(S5), 22, 4, 0, 0, -1, -1 }, + { default(S5), 22, 4, 21, 6, 4, 1 }, + { default(S5), 22, 4, 0, 0, 4, -1 }, { default(S4), 50, 12, 12, 1, 12, 12 }, { default(S4), 50, 7, 12, 2, 7, 5 }, - { default(S4), 50, 6, 12, 2, 6, 6 }, + { default(S4), 50, 6, 12, 1, 12, 12 }, { default(S4), 50, 5, 12, 2, 10, 2 }, { default(S4), 50, 4, 12, 1, 12, 12 }, { default(S4), 50, 3, 12, 1, 12, 12 }, @@ -33,31 +36,34 @@ namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers { default(S4), 50, 12, 13, 2, 12, 1 }, { default(S4), 50, 7, 21, 3, 7, 7 }, - { default(S4), 50, 7, 23, 3, 7, 2 }, + { default(S4), 50, 7, 23, 4, 7, 2 }, + { default(S4), 50, 6, 13, 2, 12, 1 }, + { default(short), 200, 50, 49, 1, 49, 49 }, + { default(short), 200, 50, 1, 1, 1, 1 }, { default(byte), 1000, 512, 2047, 4, 512, 511 } }; [Theory] [MemberData(nameof(AllocateData))] - public void CreatesBlocksOfCorrectSizes( + public void BufferSizesAreCorrect( T dummy, - int blockCapacity, - int blockAlignment, + int bufferCapacity, + int bufferAlignment, long totalLength, - int expectedNumberOfBlocks, - int expectedBlockSize, - int expectedSizeOfLastBlock) + int expectedNumberOfBuffers, + int expectedBufferSize, + int expectedSizeOfLastBuffer) where T : struct { - this.memoryAllocator.BlockCapacity = blockCapacity; + this.memoryAllocator.BufferCapacity = bufferCapacity; // Act: - using var g = MemoryGroup.Allocate(this.memoryAllocator, totalLength, blockAlignment); + using var g = MemoryGroup.Allocate(this.memoryAllocator, totalLength, bufferAlignment); // Assert: - Assert.Equal(expectedNumberOfBlocks, g.Count); - Assert.Equal(expectedBlockSize, g.BlockSize); + Assert.Equal(expectedNumberOfBuffers, g.Count); + Assert.Equal(expectedBufferSize, g.BufferSize); if (g.Count == 0) { return; @@ -65,29 +71,29 @@ namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers for (int i = 0; i < g.Count - 1; i++) { - Assert.Equal(g[i].Length, expectedBlockSize); + Assert.Equal(g[i].Length, expectedBufferSize); } - Assert.Equal(g.Last().Length, expectedSizeOfLastBlock); + Assert.Equal(g.Last().Length, expectedSizeOfLastBuffer); } [Fact] public void WhenBlockAlignmentIsOverCapacity_Throws_InvalidMemoryOperationException() { - this.memoryAllocator.BlockCapacity = 42; + this.memoryAllocator.BufferCapacity = 84; // 42 * Int16 Assert.Throws(() => { - MemoryGroup.Allocate(this.memoryAllocator, 50, 43); + MemoryGroup.Allocate(this.memoryAllocator, 50, 43); }); } [Theory] [InlineData(AllocationOptions.None)] [InlineData(AllocationOptions.Clean)] - public void MemoryAllocator_IsUtilizedCorrectly(AllocationOptions allocationOptions) + public void MemoryAllocatorIsUtilizedCorrectly(AllocationOptions allocationOptions) { - this.memoryAllocator.BlockCapacity = 200; + this.memoryAllocator.BufferCapacity = 200; HashSet bufferHashes; @@ -113,15 +119,33 @@ namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers } } + [Fact] + public void IsValid_TrueAfterCreation() + { + using var g = MemoryGroup.Allocate(this.memoryAllocator, 10, 100); + + Assert.True(g.IsValid); + } + + [Fact] + public void IsValid_FalseAfterDisposal() + { + using var g = MemoryGroup.Allocate(this.memoryAllocator, 10, 100); + + g.Dispose(); + Assert.False(g.IsValid); + } [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/TestUtilities/TestMemoryAllocator.cs b/tests/ImageSharp.Tests/TestUtilities/TestMemoryAllocator.cs index c49a68990..a77e7f2f2 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestMemoryAllocator.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestMemoryAllocator.cs @@ -26,13 +26,13 @@ namespace SixLabors.ImageSharp.Tests.Memory /// public byte DirtyValue { get; } - public int BlockCapacity { get; set; } = int.MaxValue; + public int BufferCapacity { get; set; } = int.MaxValue; public IReadOnlyList AllocationLog => this.allocationLog; public IReadOnlyList ReturnLog => this.returnLog; - protected internal override int GetBlockCapacity() => this.BlockCapacity; + protected internal override int GetBufferCapacity() => this.BufferCapacity; public override IMemoryOwner Allocate(int length, AllocationOptions options = AllocationOptions.None) { From 302c06b3a63747d89cfbbedb402feb545b6831dd Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 2 Feb 2020 23:04:19 +0100 Subject: [PATCH 11/62] add some tests for CopyTo --- .../DiscontiguousBuffers/IMemoryGroup{T}.cs | 20 ++- .../InvalidMemoryOperationException.cs | 3 + .../MemoryGroupExtensions.cs | 16 ++ .../MemoryGroupView{T}.cs | 24 +-- .../MemoryGroup{T}.Consumed.cs | 7 +- .../MemoryGroup{T}.Owned.cs | 7 +- .../DiscontiguousBuffers/MemoryGroup{T}.cs | 39 +++-- .../DiscontiguousBuffers/MemoryGroupIndex.cs | 120 ++++++++++++++ .../MemoryGroupIndexTests.cs | 64 ++++++++ .../MemoryGroupTests.Allocate.cs | 119 ++++++++++++++ .../MemoryGroupTests.CopyTo.cs | 45 ++++++ .../DiscontiguousBuffers/MemoryGroupTests.cs | 148 ++++-------------- 12 files changed, 467 insertions(+), 145 deletions(-) create mode 100644 src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupExtensions.cs create mode 100644 tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupIndex.cs create mode 100644 tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupIndexTests.cs create mode 100644 tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.Allocate.cs create mode 100644 tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.CopyTo.cs diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/IMemoryGroup{T}.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/IMemoryGroup{T}.cs index a2eafb160..2649b7fb1 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/IMemoryGroup{T}.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/IMemoryGroup{T}.cs @@ -1,3 +1,6 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + using System; using System.Collections.Generic; @@ -12,10 +15,23 @@ namespace SixLabors.ImageSharp.Memory where T : struct { /// - /// Gets the number of elements per contiguous sub-block. + /// Gets the number of elements per contiguous sub-buffer preceding the last buffer. + /// The last buffer is allowed to be smaller. + /// + public int BufferLength { get; } + + /// + /// Gets the aggregate number of elements in the group. /// - public int BufferSize { get; } + public long TotalLength { get; } + /// + /// Gets a value indicating whether the group has been invalidated. + /// + /// + /// Invalidation usually occurs when an image processor capable to alter the image dimensions replaces + /// the image buffers internally. + /// bool IsValid { get; } } } diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/InvalidMemoryOperationException.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/InvalidMemoryOperationException.cs index df30c2ee2..51ed7e861 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/InvalidMemoryOperationException.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/InvalidMemoryOperationException.cs @@ -1,3 +1,6 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + using System; namespace SixLabors.ImageSharp.Memory diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupExtensions.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupExtensions.cs new file mode 100644 index 000000000..3e0df15ea --- /dev/null +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupExtensions.cs @@ -0,0 +1,16 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Memory +{ + internal static class MemoryGroupExtensions + { + public static void CopyTo(this IMemoryGroup source, IMemoryGroup target) + where T : struct + { + throw new NotImplementedException(); + } + } +} diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupView{T}.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupView{T}.cs index b1077e254..ec801015a 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupView{T}.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupView{T}.cs @@ -1,3 +1,6 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + using System; using System.Buffers; using System.Collections; @@ -15,7 +18,8 @@ namespace SixLabors.ImageSharp.Memory /// instance becomes invalid, throwing an exception on all operations. /// /// The element type. - internal class MemoryGroupView : IMemoryGroup where T : struct + internal class MemoryGroupView : IMemoryGroup + where T : struct { private readonly Memory.MemoryGroup owner; private readonly MemoryOwnerWrapper[] memoryWrappers; @@ -32,23 +36,25 @@ namespace SixLabors.ImageSharp.Memory } } - public IEnumerator> GetEnumerator() => throw new NotImplementedException(); + public int Count => this.owner.Count; - IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); + public int BufferLength => this.owner.BufferLength; + + public long TotalLength => this.owner.TotalLength; - public int Count { get; } + public bool IsValid { get; internal set; } public Memory this[int index] => throw new NotImplementedException(); - public int BufferSize => this.owner.BufferSize; + public IEnumerator> GetEnumerator() => throw new NotImplementedException(); - public bool IsValid { get; internal set; } + IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); - class MemoryOwnerWrapper : MemoryManager + private class MemoryOwnerWrapper : MemoryManager { - private MemoryGroupView view; + private readonly MemoryGroupView view; - private int index; + private readonly int index; public MemoryOwnerWrapper(MemoryGroupView view, int index) { diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs index 20afb2d57..4b7f8acae 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs @@ -1,3 +1,6 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + using System; using System.Collections.Generic; @@ -10,8 +13,8 @@ namespace SixLabors.ImageSharp.Memory { private readonly ReadOnlyMemory> source; - public Consumed(ReadOnlyMemory> source, int bufferSize) - : base(bufferSize) + public Consumed(ReadOnlyMemory> source, int bufferLength, long totalLength) + : base(bufferLength, totalLength) { this.source = source; } diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs index c90a24376..e0dfc6396 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs @@ -1,3 +1,6 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + using System; using System.Buffers; using System.Collections.Generic; @@ -12,8 +15,8 @@ namespace SixLabors.ImageSharp.Memory { private IMemoryOwner[] memoryOwners; - public Owned(IMemoryOwner[] memoryOwners, int bufferSize) - : base(bufferSize) + public Owned(IMemoryOwner[] memoryOwners, int bufferLength, long totalLength) + : base(bufferLength, totalLength) { this.memoryOwners = memoryOwners; } diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs index 9d36cc8dc..ac43d6847 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs @@ -1,3 +1,6 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + using System; using System.Buffers; using System.Collections; @@ -17,11 +20,17 @@ namespace SixLabors.ImageSharp.Memory { private static readonly int ElementSize = Unsafe.SizeOf(); - private MemoryGroup(int bufferSize) => this.BufferSize = bufferSize; + private MemoryGroup(int bufferLength, long totalLength) + { + this.BufferLength = bufferLength; + this.TotalLength = totalLength; + } public abstract int Count { get; } - public int BufferSize { get; } + public int BufferLength { get; } + + public long TotalLength { get; } public bool IsValid { get; private set; } = true; @@ -51,18 +60,18 @@ namespace SixLabors.ImageSharp.Memory } int numberOfAlignedSegments = blockCapacityInElements / blockAlignment; - int bufferSize = numberOfAlignedSegments * blockAlignment; - if (totalLength > 0 && totalLength < bufferSize) + int bufferLength = numberOfAlignedSegments * blockAlignment; + if (totalLength > 0 && totalLength < bufferLength) { - bufferSize = (int)totalLength; + bufferLength = (int)totalLength; } - int sizeOfLastBuffer = (int)(totalLength % bufferSize); - long bufferCount = totalLength / bufferSize; + int sizeOfLastBuffer = (int)(totalLength % bufferLength); + long bufferCount = totalLength / bufferLength; if (sizeOfLastBuffer == 0) { - sizeOfLastBuffer = bufferSize; + sizeOfLastBuffer = bufferLength; } else { @@ -72,7 +81,7 @@ namespace SixLabors.ImageSharp.Memory var buffers = new IMemoryOwner[bufferCount]; for (int i = 0; i < buffers.Length - 1; i++) { - buffers[i] = allocator.Allocate(bufferSize, allocationOptions); + buffers[i] = allocator.Allocate(bufferLength, allocationOptions); } if (bufferCount > 0) @@ -80,28 +89,30 @@ namespace SixLabors.ImageSharp.Memory buffers[^1] = allocator.Allocate(sizeOfLastBuffer, allocationOptions); } - return new Owned(buffers, bufferSize); + return new Owned(buffers, bufferLength, totalLength); } public static MemoryGroup Wrap(params Memory[] source) => Wrap(source.AsMemory()); public static MemoryGroup Wrap(ReadOnlyMemory> source) { - int bufferSize = source.Length > 0 ? source.Span[0].Length : 0; + int bufferLength = source.Length > 0 ? source.Span[0].Length : 0; for (int i = 1; i < source.Length - 1; i++) { - if (source.Span[i].Length != bufferSize) + if (source.Span[i].Length != bufferLength) { throw new InvalidMemoryOperationException("Wrap: buffers should be uniformly sized!"); } } - if (source.Length > 0 && source.Span[^1].Length > bufferSize) + if (source.Length > 0 && source.Span[^1].Length > bufferLength) { throw new InvalidMemoryOperationException("Wrap: the last buffer is too large!"); } - return new Consumed(source, bufferSize); + long totalLength = bufferLength > 0 ? ((long)bufferLength * (source.Length - 1)) + source.Span[^1].Length : 0; + + return new Consumed(source, bufferLength, totalLength); } // Analogous to current MemorySource.SwapOrCopyContent() diff --git a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupIndex.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupIndex.cs new file mode 100644 index 000000000..710f90216 --- /dev/null +++ b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupIndex.cs @@ -0,0 +1,120 @@ +using System; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers +{ + public struct MemoryGroupIndex : IEquatable + { + public override bool Equals(object obj) => obj is MemoryGroupIndex other && this.Equals(other); + + public override int GetHashCode() => HashCode.Combine(this.BufferLength, this.BufferIndex, this.ElementIndex); + + public int BufferLength { get; } + + public int BufferIndex { get; } + + public int ElementIndex { get; } + + public MemoryGroupIndex(int bufferLength, int bufferIndex, int elementIndex) + { + this.BufferLength = bufferLength; + this.BufferIndex = bufferIndex; + this.ElementIndex = elementIndex; + } + + public static MemoryGroupIndex operator +(MemoryGroupIndex idx, int val) + { + int nextElementIndex = idx.ElementIndex + val; + return new MemoryGroupIndex( + idx.BufferLength, + idx.BufferIndex + (nextElementIndex / idx.BufferLength), + nextElementIndex % idx.BufferLength); + } + + public bool Equals(MemoryGroupIndex other) + { + if (this.BufferLength != other.BufferLength) + { + throw new InvalidOperationException(); + } + + return this.BufferIndex == other.BufferIndex && this.ElementIndex == other.ElementIndex; + } + + public static bool operator ==(MemoryGroupIndex a, MemoryGroupIndex b) => a.Equals(b); + + public static bool operator !=(MemoryGroupIndex a, MemoryGroupIndex b) => !a.Equals(b); + + public static bool operator <(MemoryGroupIndex a, MemoryGroupIndex b) + { + if (a.BufferLength != b.BufferLength) + { + throw new InvalidOperationException(); + } + + if (a.BufferIndex < b.BufferIndex) + { + return true; + } + + if (a.BufferIndex == b.BufferIndex) + { + return a.ElementIndex < b.ElementIndex; + } + + return false; + } + + public static bool operator >(MemoryGroupIndex a, MemoryGroupIndex b) + { + if (a.BufferLength != b.BufferLength) + { + throw new InvalidOperationException(); + } + + if (a.BufferIndex > b.BufferIndex) + { + return true; + } + + if (a.BufferIndex == b.BufferIndex) + { + return a.ElementIndex > b.ElementIndex; + } + + return false; + } + } + + internal static class MemoryGroupIndexExtensions + { + public static T GetElementAt(this MemoryGroup group, MemoryGroupIndex idx) + where T : struct + { + return group[idx.BufferIndex].Span[idx.ElementIndex]; + } + + public static void SetElementAt(this MemoryGroup group, MemoryGroupIndex idx, T value) + where T : struct + { + group[idx.BufferIndex].Span[idx.ElementIndex] = value; + } + + public static MemoryGroupIndex MinIndex(this MemoryGroup group) + where T : struct + { + return new MemoryGroupIndex(group.BufferLength, 0, 0); + } + + public static MemoryGroupIndex MaxIndex(this MemoryGroup group) + where T : struct + { + if (group.Count == 0) + { + return new MemoryGroupIndex(group.BufferLength, 0, 0); + } + + return new MemoryGroupIndex(group.BufferLength, group.Count - 1, group[^1].Length - 1); + } + } +} diff --git a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupIndexTests.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupIndexTests.cs new file mode 100644 index 000000000..6a9d322f6 --- /dev/null +++ b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupIndexTests.cs @@ -0,0 +1,64 @@ +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers +{ + public class MemoryGroupIndexTests + { + [Fact] + public void Equal() + { + var a = new MemoryGroupIndex(10, 1, 3); + var b = new MemoryGroupIndex(10, 1, 3); + + Assert.True(a.Equals(b)); + Assert.True(a == b); + Assert.False(a != b); + Assert.False(a < b); + Assert.False(a > b); + } + + [Fact] + public void SmallerBufferIndex() + { + var a = new MemoryGroupIndex(10, 3, 3); + var b = new MemoryGroupIndex(10, 5, 3); + + Assert.False(a == b); + Assert.True(a != b); + Assert.True(a < b); + Assert.False(a > b); + } + + [Fact] + public void SmallerElementIndex() + { + var a = new MemoryGroupIndex(10, 3, 3); + var b = new MemoryGroupIndex(10, 3, 9); + + Assert.False(a == b); + Assert.True(a != b); + Assert.True(a < b); + Assert.False(a > b); + } + + [Fact] + public void Increment() + { + var a = new MemoryGroupIndex(10, 3, 3); + a += 1; + Assert.Equal(new MemoryGroupIndex(10, 3, 4), a); + } + + [Fact] + public void Increment_OverflowBuffer() + { + var a = new MemoryGroupIndex(10, 5, 3); + var b = new MemoryGroupIndex(10, 5, 9); + a += 8; + b += 1; + + Assert.Equal(new MemoryGroupIndex(10, 6, 1), a); + Assert.Equal(new MemoryGroupIndex(10, 6, 0), b); + } + } +} diff --git a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.Allocate.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.Allocate.cs new file mode 100644 index 000000000..1a617d396 --- /dev/null +++ b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.Allocate.cs @@ -0,0 +1,119 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Collections.Generic; +using System.Linq; +using SixLabors.ImageSharp.Memory; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers +{ + public partial class MemoryGroupTests + { + public class Allocate : MemoryGroupTestsBase + { +#pragma warning disable SA1509 + public static TheoryData AllocateData = + new TheoryData() + { + { default(S5), 22, 4, 4, 1, 4, 4 }, + { default(S5), 22, 4, 7, 2, 4, 3 }, + { default(S5), 22, 4, 8, 2, 4, 4 }, + { default(S5), 22, 4, 21, 6, 4, 1 }, + { default(S5), 22, 4, 0, 0, 4, -1 }, + + { default(S4), 50, 12, 12, 1, 12, 12 }, + { default(S4), 50, 7, 12, 2, 7, 5 }, + { default(S4), 50, 6, 12, 1, 12, 12 }, + { default(S4), 50, 5, 12, 2, 10, 2 }, + { default(S4), 50, 4, 12, 1, 12, 12 }, + { default(S4), 50, 3, 12, 1, 12, 12 }, + { default(S4), 50, 2, 12, 1, 12, 12 }, + { default(S4), 50, 1, 12, 1, 12, 12 }, + + { default(S4), 50, 12, 13, 2, 12, 1 }, + { 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(short), 200, 50, 49, 1, 49, 49 }, + { default(short), 200, 50, 1, 1, 1, 1 }, + { default(byte), 1000, 512, 2047, 4, 512, 511 } + }; + + [Theory] + [MemberData(nameof(AllocateData))] + public void BufferSizesAreCorrect( + T dummy, + int bufferCapacity, + int bufferAlignment, + long totalLength, + int expectedNumberOfBuffers, + int expectedBufferSize, + int expectedSizeOfLastBuffer) + where T : struct + { + this.MemoryAllocator.BufferCapacity = bufferCapacity; + + // Act: + using var g = MemoryGroup.Allocate(this.MemoryAllocator, totalLength, bufferAlignment); + + // Assert: + Assert.Equal(expectedNumberOfBuffers, g.Count); + Assert.Equal(expectedBufferSize, g.BufferLength); + if (g.Count == 0) + { + return; + } + + for (int i = 0; i < g.Count - 1; i++) + { + Assert.Equal(g[i].Length, expectedBufferSize); + } + + Assert.Equal(g.Last().Length, expectedSizeOfLastBuffer); + } + + [Fact] + public void WhenBlockAlignmentIsOverCapacity_Throws_InvalidMemoryOperationException() + { + this.MemoryAllocator.BufferCapacity = 84; // 42 * Int16 + + Assert.Throws(() => + { + MemoryGroup.Allocate(this.MemoryAllocator, 50, 43); + }); + } + + [Theory] + [InlineData(AllocationOptions.None)] + [InlineData(AllocationOptions.Clean)] + public void MemoryAllocatorIsUtilizedCorrectly(AllocationOptions allocationOptions) + { + this.MemoryAllocator.BufferCapacity = 200; + + HashSet bufferHashes; + + int expectedBlockCount = 5; + using (var g = MemoryGroup.Allocate(this.MemoryAllocator, 500, 100, allocationOptions)) + { + IReadOnlyList allocationLog = this.MemoryAllocator.AllocationLog; + Assert.Equal(expectedBlockCount, allocationLog.Count); + bufferHashes = allocationLog.Select(l => l.HashCodeOfBuffer).ToHashSet(); + Assert.Equal(expectedBlockCount, bufferHashes.Count); + Assert.Equal(0, this.MemoryAllocator.ReturnLog.Count); + + for (int i = 0; i < expectedBlockCount; i++) + { + Assert.Equal(allocationOptions, allocationLog[i].AllocationOptions); + Assert.Equal(100, allocationLog[i].Length); + Assert.Equal(200, allocationLog[i].LengthInBytes); + } + } + + Assert.Equal(expectedBlockCount, this.MemoryAllocator.ReturnLog.Count); + Assert.True(bufferHashes.SetEquals(this.MemoryAllocator.ReturnLog.Select(l => l.HashCodeOfBuffer))); + } + } + } +} diff --git a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.CopyTo.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.CopyTo.cs new file mode 100644 index 000000000..206d6499b --- /dev/null +++ b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.CopyTo.cs @@ -0,0 +1,45 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Memory; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers +{ + public partial class MemoryGroupTests + { + public class CopyTo : MemoryGroupTestsBase + { + public static readonly TheoryData WhenSourceBufferIsShorterOrEqual_Data = + new TheoryData() + { + { 20, 5, 20, 4 }, + { 20, 4, 20, 5 }, + { 18, 6, 20, 5 }, + { 19, 10, 20, 10 }, + { 21, 10, 22, 2 }, + { 1, 5, 5, 4 }, + }; + + [Theory] + [MemberData(nameof(WhenSourceBufferIsShorterOrEqual_Data))] + public void WhenSourceBufferIsShorterOrEqual(int srcTotal, int srcBufLen, int trgTotal, int trgBufLen) + { + using MemoryGroup src = this.CreateTestGroup(srcTotal, srcBufLen, true); + using MemoryGroup trg = this.CreateTestGroup(trgTotal, trgBufLen, false); + + src.CopyTo(trg); + + MemoryGroupIndex i = src.MinIndex(); + MemoryGroupIndex j = trg.MinIndex(); + for (; i < src.MaxIndex(); i += 1, j += 1) + { + int a = src.GetElementAt(i); + int b = src.GetElementAt(j); + + Assert.Equal(a, b); + } + } + } + } +} diff --git a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs index c12c09b7a..b9fea3497 100644 --- a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs +++ b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs @@ -1,128 +1,18 @@ -using System.Collections.Generic; -using System.Linq; +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + using System.Runtime.InteropServices; using SixLabors.ImageSharp.Memory; using Xunit; -using Xunit.Sdk; namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers { - public class MemoryGroupTests + public partial class MemoryGroupTests : MemoryGroupTestsBase { - private readonly TestMemoryAllocator memoryAllocator = new TestMemoryAllocator(); - - public class Allocate - { - private readonly TestMemoryAllocator memoryAllocator = new TestMemoryAllocator(); - -#pragma warning disable SA1509 - public static TheoryData AllocateData = - new TheoryData() - { - { default(S5), 22, 4, 4, 1, 4, 4 }, - { default(S5), 22, 4, 7, 2, 4, 3 }, - { default(S5), 22, 4, 8, 2, 4, 4 }, - { default(S5), 22, 4, 21, 6, 4, 1 }, - { default(S5), 22, 4, 0, 0, 4, -1 }, - - { default(S4), 50, 12, 12, 1, 12, 12 }, - { default(S4), 50, 7, 12, 2, 7, 5 }, - { default(S4), 50, 6, 12, 1, 12, 12 }, - { default(S4), 50, 5, 12, 2, 10, 2 }, - { default(S4), 50, 4, 12, 1, 12, 12 }, - { default(S4), 50, 3, 12, 1, 12, 12 }, - { default(S4), 50, 2, 12, 1, 12, 12 }, - { default(S4), 50, 1, 12, 1, 12, 12 }, - - { default(S4), 50, 12, 13, 2, 12, 1 }, - { 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(short), 200, 50, 49, 1, 49, 49 }, - { default(short), 200, 50, 1, 1, 1, 1 }, - { default(byte), 1000, 512, 2047, 4, 512, 511 } - }; - - [Theory] - [MemberData(nameof(AllocateData))] - public void BufferSizesAreCorrect( - T dummy, - int bufferCapacity, - int bufferAlignment, - long totalLength, - int expectedNumberOfBuffers, - int expectedBufferSize, - int expectedSizeOfLastBuffer) - where T : struct - { - this.memoryAllocator.BufferCapacity = bufferCapacity; - - // Act: - using var g = MemoryGroup.Allocate(this.memoryAllocator, totalLength, bufferAlignment); - - // Assert: - Assert.Equal(expectedNumberOfBuffers, g.Count); - Assert.Equal(expectedBufferSize, g.BufferSize); - if (g.Count == 0) - { - return; - } - - for (int i = 0; i < g.Count - 1; i++) - { - Assert.Equal(g[i].Length, expectedBufferSize); - } - - Assert.Equal(g.Last().Length, expectedSizeOfLastBuffer); - } - - [Fact] - public void WhenBlockAlignmentIsOverCapacity_Throws_InvalidMemoryOperationException() - { - this.memoryAllocator.BufferCapacity = 84; // 42 * Int16 - - Assert.Throws(() => - { - MemoryGroup.Allocate(this.memoryAllocator, 50, 43); - }); - } - - [Theory] - [InlineData(AllocationOptions.None)] - [InlineData(AllocationOptions.Clean)] - public void MemoryAllocatorIsUtilizedCorrectly(AllocationOptions allocationOptions) - { - this.memoryAllocator.BufferCapacity = 200; - - HashSet bufferHashes; - - int expectedBlockCount = 5; - using (var g = MemoryGroup.Allocate(this.memoryAllocator, 500, 100, allocationOptions)) - { - IReadOnlyList allocationLog = this.memoryAllocator.AllocationLog; - Assert.Equal(expectedBlockCount, allocationLog.Count); - bufferHashes = allocationLog.Select(l => l.HashCodeOfBuffer).ToHashSet(); - Assert.Equal(expectedBlockCount, bufferHashes.Count); - Assert.Equal(0, this.memoryAllocator.ReturnLog.Count); - - for (int i = 0; i < expectedBlockCount; i++) - { - Assert.Equal(allocationOptions, allocationLog[i].AllocationOptions); - Assert.Equal(100, allocationLog[i].Length); - Assert.Equal(200, allocationLog[i].LengthInBytes); - } - } - - Assert.Equal(expectedBlockCount, this.memoryAllocator.ReturnLog.Count); - Assert.True(bufferHashes.SetEquals(this.memoryAllocator.ReturnLog.Select(l => l.HashCodeOfBuffer))); - } - } - [Fact] public void IsValid_TrueAfterCreation() { - using var g = MemoryGroup.Allocate(this.memoryAllocator, 10, 100); + using var g = MemoryGroup.Allocate(this.MemoryAllocator, 10, 100); Assert.True(g.IsValid); } @@ -130,12 +20,13 @@ namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers [Fact] public void IsValid_FalseAfterDisposal() { - using var g = MemoryGroup.Allocate(this.memoryAllocator, 10, 100); + using var g = MemoryGroup.Allocate(this.MemoryAllocator, 10, 100); g.Dispose(); Assert.False(g.IsValid); } + [StructLayout(LayoutKind.Sequential, Size = 5)] private struct S5 { @@ -148,4 +39,29 @@ namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers public override string ToString() => "S4"; } } + + public abstract class MemoryGroupTestsBase + { + internal readonly TestMemoryAllocator MemoryAllocator = new TestMemoryAllocator(); + + internal MemoryGroup CreateTestGroup(long totalLength, int bufferLength, bool fillSequence = false) + { + this.MemoryAllocator.BufferCapacity = bufferLength; + var g = MemoryGroup.Allocate(this.MemoryAllocator, totalLength, bufferLength); + + if (!fillSequence) + { + return g; + } + + int j = 1; + for (MemoryGroupIndex i = g.MinIndex(); i < g.MaxIndex(); i += 1) + { + g.SetElementAt(i, j); + j++; + } + + return g; + } + } } From e93de01c2c4f16811befb2ce88a954a719cae728 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 2 Feb 2020 23:51:48 +0100 Subject: [PATCH 12/62] CopyTo WIP --- .../Allocators/ArrayPoolMemoryAllocator.cs | 12 +-- .../Memory/Allocators/MemoryAllocator.cs | 4 +- .../Allocators/SimpleGcMemoryAllocator.cs | 4 +- .../MemoryGroupExtensions.cs | 83 ++++++++++++++++++- .../DiscontiguousBuffers/MemoryGroup{T}.cs | 2 +- .../DiscontiguousBuffers/MemoryGroupIndex.cs | 5 +- .../MemoryGroupIndexTests.cs | 5 +- .../MemoryGroupTests.Allocate.cs | 6 +- .../MemoryGroupTests.CopyTo.cs | 5 ++ .../DiscontiguousBuffers/MemoryGroupTests.cs | 26 ------ .../MemoryGroupTestsBase.cs | 32 +++++++ .../TestUtilities/TestMemoryAllocator.cs | 4 +- 12 files changed, 144 insertions(+), 44 deletions(-) create mode 100644 tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTestsBase.cs diff --git a/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.cs index d341e93af..883d57851 100644 --- a/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.cs +++ b/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.cs @@ -83,19 +83,19 @@ namespace SixLabors.ImageSharp.Memory /// public int PoolSelectorThresholdInBytes { get; } + /// + /// Gets or sets the length of the largest contiguous buffer that can be handled by this allocator instance. + /// + public int BufferCapacityInBytes { get; set; } = DefaultBufferCapacity; + /// public override void ReleaseRetainedResources() { this.InitArrayPools(); } - /// - /// Gets or sets the length of the largest contiguous buffer that can be handled by this allocator instance. - /// - public int MaximumContiguousBufferLength { get; set; } = Int32.MaxValue; - /// - protected internal override int GetBufferCapacity() => this.MaximumContiguousBufferLength; + protected internal override int GetBufferCapacityInBytes() => this.BufferCapacityInBytes; /// public override IMemoryOwner Allocate(int length, AllocationOptions options = AllocationOptions.None) diff --git a/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs index 9ed322c9c..c6e92f23f 100644 --- a/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs +++ b/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs @@ -10,11 +10,13 @@ namespace SixLabors.ImageSharp.Memory /// public abstract class MemoryAllocator { + internal const int DefaultBufferCapacity = int.MaxValue / 2; + /// /// Gets the length of the largest contiguous buffer that can be handled by this allocator instance in bytes. /// /// The length of the largest contiguous buffer that can be handled by this allocator instance. - protected internal abstract int GetBufferCapacity(); + protected internal abstract int GetBufferCapacityInBytes(); /// /// Allocates an , holding a of length . diff --git a/src/ImageSharp/Memory/Allocators/SimpleGcMemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/SimpleGcMemoryAllocator.cs index 293e807ef..b417df351 100644 --- a/src/ImageSharp/Memory/Allocators/SimpleGcMemoryAllocator.cs +++ b/src/ImageSharp/Memory/Allocators/SimpleGcMemoryAllocator.cs @@ -7,12 +7,12 @@ using SixLabors.ImageSharp.Memory.Internals; namespace SixLabors.ImageSharp.Memory { /// - /// Implements by newing up arrays by the GC on every allocation requests. + /// Implements by newing up managed arrays on every allocation request. /// public sealed class SimpleGcMemoryAllocator : MemoryAllocator { /// - protected internal override int GetBufferCapacity() => int.MaxValue; + protected internal override int GetBufferCapacityInBytes() => DefaultBufferCapacity; /// public override IMemoryOwner Allocate(int length, AllocationOptions options = AllocationOptions.None) diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupExtensions.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupExtensions.cs index 3e0df15ea..68a1f2e80 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupExtensions.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupExtensions.cs @@ -10,7 +10,88 @@ namespace SixLabors.ImageSharp.Memory public static void CopyTo(this IMemoryGroup source, IMemoryGroup target) where T : struct { - throw new NotImplementedException(); + Guard.NotNull(source, nameof(source)); + Guard.NotNull(target, nameof(target)); + Guard.IsTrue(source.IsValid, nameof(source), "Source group must be valid."); + Guard.IsTrue(target.IsValid, nameof(target), "Target group must be valid."); + Guard.MustBeLessThanOrEqualTo(source.TotalLength, target.TotalLength, "Destination buffer too short!"); + + if (source.IsEmpty()) + { + return; + } + + long position = 0; + var srcCur = new MemoryGroupCursor(source); + var trgCur = new MemoryGroupCursor(target); + + while (position < source.TotalLength) + { + int fwd = Math.Min(srcCur.LookAhead(), trgCur.LookAhead()); + Span srcSpan = srcCur.GetSpan(fwd); + Span trgSpan = trgCur.GetSpan(fwd); + srcSpan.CopyTo(trgSpan); + + srcCur.Forward(fwd); + trgCur.Forward(fwd); + position += fwd; + } + } + + public static bool IsEmpty(this IMemoryGroup group) + where T : struct + => group.Count == 0; + + private struct MemoryGroupCursor + where T : struct + { + private readonly IMemoryGroup memoryGroup; + + private int bufferIndex; + + private int elementIndex; + + public MemoryGroupCursor(IMemoryGroup memoryGroup) + { + this.memoryGroup = memoryGroup; + this.bufferIndex = 0; + this.elementIndex = 0; + } + + private bool IsAtLastBuffer => this.bufferIndex == this.memoryGroup.Count - 1; + + private int CurrentBufferLength => this.memoryGroup[this.bufferIndex].Length; + + public Span GetSpan(int length) + { + return this.memoryGroup[this.bufferIndex].Span.Slice(this.elementIndex, length); + } + + public int LookAhead() + { + return this.CurrentBufferLength - this.elementIndex; + } + + public void Forward(int steps) + { + int nextIdx = this.elementIndex + steps; + int currentBufferLength = this.CurrentBufferLength; + + if (nextIdx < currentBufferLength) + { + this.elementIndex = nextIdx; + } + else if (nextIdx == currentBufferLength) + { + this.bufferIndex++; + this.elementIndex = 0; + } + else + { + // If we get here, it indicates a bug in CopyTo: + throw new ArgumentException("Can't forward multiple buffers!", nameof(steps)); + } + } } } } diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs index ac43d6847..3883a111d 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs @@ -53,7 +53,7 @@ namespace SixLabors.ImageSharp.Memory Guard.MustBeGreaterThanOrEqualTo(totalLength, 0, nameof(totalLength)); Guard.MustBeGreaterThan(blockAlignment, 0, nameof(blockAlignment)); - int blockCapacityInElements = allocator.GetBufferCapacity() / ElementSize; + int blockCapacityInElements = allocator.GetBufferCapacityInBytes() / ElementSize; if (blockAlignment > blockCapacityInElements) { throw new InvalidMemoryOperationException(); diff --git a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupIndex.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupIndex.cs index 710f90216..d619aec29 100644 --- a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupIndex.cs +++ b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupIndex.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers diff --git a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupIndexTests.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupIndexTests.cs index 6a9d322f6..f0cc18f29 100644 --- a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupIndexTests.cs +++ b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupIndexTests.cs @@ -1,4 +1,7 @@ -using Xunit; +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using Xunit; namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers { diff --git a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.Allocate.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.Allocate.cs index 1a617d396..0c96f3d78 100644 --- a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.Allocate.cs +++ b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.Allocate.cs @@ -53,7 +53,7 @@ namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers int expectedSizeOfLastBuffer) where T : struct { - this.MemoryAllocator.BufferCapacity = bufferCapacity; + this.MemoryAllocator.BufferCapacityInBytes = bufferCapacity; // Act: using var g = MemoryGroup.Allocate(this.MemoryAllocator, totalLength, bufferAlignment); @@ -77,7 +77,7 @@ namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers [Fact] public void WhenBlockAlignmentIsOverCapacity_Throws_InvalidMemoryOperationException() { - this.MemoryAllocator.BufferCapacity = 84; // 42 * Int16 + this.MemoryAllocator.BufferCapacityInBytes = 84; // 42 * Int16 Assert.Throws(() => { @@ -90,7 +90,7 @@ namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers [InlineData(AllocationOptions.Clean)] public void MemoryAllocatorIsUtilizedCorrectly(AllocationOptions allocationOptions) { - this.MemoryAllocator.BufferCapacity = 200; + this.MemoryAllocator.BufferCapacityInBytes = 200; HashSet bufferHashes; diff --git a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.CopyTo.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.CopyTo.cs index 206d6499b..fd679d071 100644 --- a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.CopyTo.cs +++ b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.CopyTo.cs @@ -10,15 +10,20 @@ namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers { public class CopyTo : MemoryGroupTestsBase { +#pragma warning disable SA1509 public static readonly TheoryData WhenSourceBufferIsShorterOrEqual_Data = new TheoryData() { + { 20, 10, 20, 10 }, { 20, 5, 20, 4 }, { 20, 4, 20, 5 }, { 18, 6, 20, 5 }, { 19, 10, 20, 10 }, { 21, 10, 22, 2 }, { 1, 5, 5, 4 }, + + { 30, 12, 40, 5 }, + { 30, 5, 40, 12 }, }; [Theory] diff --git a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs index b9fea3497..f7865b00c 100644 --- a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs +++ b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs @@ -26,7 +26,6 @@ namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers Assert.False(g.IsValid); } - [StructLayout(LayoutKind.Sequential, Size = 5)] private struct S5 { @@ -39,29 +38,4 @@ namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers public override string ToString() => "S4"; } } - - public abstract class MemoryGroupTestsBase - { - internal readonly TestMemoryAllocator MemoryAllocator = new TestMemoryAllocator(); - - internal MemoryGroup CreateTestGroup(long totalLength, int bufferLength, bool fillSequence = false) - { - this.MemoryAllocator.BufferCapacity = bufferLength; - var g = MemoryGroup.Allocate(this.MemoryAllocator, totalLength, bufferLength); - - if (!fillSequence) - { - return g; - } - - int j = 1; - for (MemoryGroupIndex i = g.MinIndex(); i < g.MaxIndex(); i += 1) - { - g.SetElementAt(i, j); - j++; - } - - return g; - } - } } diff --git a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTestsBase.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTestsBase.cs new file mode 100644 index 000000000..acd24e343 --- /dev/null +++ b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTestsBase.cs @@ -0,0 +1,32 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers +{ + public abstract class MemoryGroupTestsBase + { + internal readonly TestMemoryAllocator MemoryAllocator = new TestMemoryAllocator(); + + internal MemoryGroup CreateTestGroup(long totalLength, int bufferLength, bool fillSequence = false) + { + this.MemoryAllocator.BufferCapacityInBytes = bufferLength * sizeof(int); + var g = MemoryGroup.Allocate(this.MemoryAllocator, totalLength, bufferLength); + + if (!fillSequence) + { + return g; + } + + int j = 1; + for (MemoryGroupIndex i = g.MinIndex(); i < g.MaxIndex(); i += 1) + { + g.SetElementAt(i, j); + j++; + } + + return g; + } + } +} diff --git a/tests/ImageSharp.Tests/TestUtilities/TestMemoryAllocator.cs b/tests/ImageSharp.Tests/TestUtilities/TestMemoryAllocator.cs index a77e7f2f2..dd928cb75 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestMemoryAllocator.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestMemoryAllocator.cs @@ -26,13 +26,13 @@ namespace SixLabors.ImageSharp.Tests.Memory /// public byte DirtyValue { get; } - public int BufferCapacity { get; set; } = int.MaxValue; + public int BufferCapacityInBytes { get; set; } = int.MaxValue; public IReadOnlyList AllocationLog => this.allocationLog; public IReadOnlyList ReturnLog => this.returnLog; - protected internal override int GetBufferCapacity() => this.BufferCapacity; + protected internal override int GetBufferCapacityInBytes() => this.BufferCapacityInBytes; public override IMemoryOwner Allocate(int length, AllocationOptions options = AllocationOptions.None) { From 7edc41814111e5f8d023f06200545edff96556d7 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Mon, 3 Feb 2020 00:31:12 +0100 Subject: [PATCH 13/62] Implemented: CopyTo, TransformTo, TransformInplace --- .../MemoryGroupExtensions.cs | 46 +++++++ .../Memory/TransformItemsDelegate{T}.cs | 9 ++ .../Memory/TransformItemsInplaceDelegate.cs | 9 ++ .../MemoryGroupTests.CopyTo.cs | 50 -------- .../DiscontiguousBuffers/MemoryGroupTests.cs | 119 ++++++++++++++++++ 5 files changed, 183 insertions(+), 50 deletions(-) create mode 100644 src/ImageSharp/Memory/TransformItemsDelegate{T}.cs create mode 100644 src/ImageSharp/Memory/TransformItemsInplaceDelegate.cs delete mode 100644 tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.CopyTo.cs diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupExtensions.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupExtensions.cs index 68a1f2e80..14e676dbe 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupExtensions.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupExtensions.cs @@ -38,6 +38,52 @@ namespace SixLabors.ImageSharp.Memory } } + public static void TransformTo( + this IMemoryGroup source, + IMemoryGroup target, + TransformItemsDelegate transform) + where T : struct + { + Guard.NotNull(source, nameof(source)); + Guard.NotNull(target, nameof(target)); + Guard.NotNull(transform, nameof(transform)); + Guard.IsTrue(source.IsValid, nameof(source), "Source group must be valid."); + Guard.IsTrue(target.IsValid, nameof(target), "Target group must be valid."); + Guard.MustBeLessThanOrEqualTo(source.TotalLength, target.TotalLength, "Destination buffer too short!"); + + if (source.IsEmpty()) + { + return; + } + + long position = 0; + var srcCur = new MemoryGroupCursor(source); + var trgCur = new MemoryGroupCursor(target); + + while (position < source.TotalLength) + { + int fwd = Math.Min(srcCur.LookAhead(), trgCur.LookAhead()); + Span srcSpan = srcCur.GetSpan(fwd); + Span trgSpan = trgCur.GetSpan(fwd); + transform(srcSpan, trgSpan); + + srcCur.Forward(fwd); + trgCur.Forward(fwd); + position += fwd; + } + } + + public static void TransformInplace( + this IMemoryGroup memoryGroup, + TransformItemsInplaceDelegate transform) + where T : struct + { + foreach (Memory memory in memoryGroup) + { + transform(memory.Span); + } + } + public static bool IsEmpty(this IMemoryGroup group) where T : struct => group.Count == 0; diff --git a/src/ImageSharp/Memory/TransformItemsDelegate{T}.cs b/src/ImageSharp/Memory/TransformItemsDelegate{T}.cs new file mode 100644 index 000000000..898d704f0 --- /dev/null +++ b/src/ImageSharp/Memory/TransformItemsDelegate{T}.cs @@ -0,0 +1,9 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Memory +{ + internal delegate void TransformItemsDelegate(ReadOnlySpan source, Span target); +} diff --git a/src/ImageSharp/Memory/TransformItemsInplaceDelegate.cs b/src/ImageSharp/Memory/TransformItemsInplaceDelegate.cs new file mode 100644 index 000000000..023606f52 --- /dev/null +++ b/src/ImageSharp/Memory/TransformItemsInplaceDelegate.cs @@ -0,0 +1,9 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Memory +{ + internal delegate void TransformItemsInplaceDelegate(Span data); +} diff --git a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.CopyTo.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.CopyTo.cs deleted file mode 100644 index fd679d071..000000000 --- a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.CopyTo.cs +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.Memory; -using Xunit; - -namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers -{ - public partial class MemoryGroupTests - { - public class CopyTo : MemoryGroupTestsBase - { -#pragma warning disable SA1509 - public static readonly TheoryData WhenSourceBufferIsShorterOrEqual_Data = - new TheoryData() - { - { 20, 10, 20, 10 }, - { 20, 5, 20, 4 }, - { 20, 4, 20, 5 }, - { 18, 6, 20, 5 }, - { 19, 10, 20, 10 }, - { 21, 10, 22, 2 }, - { 1, 5, 5, 4 }, - - { 30, 12, 40, 5 }, - { 30, 5, 40, 12 }, - }; - - [Theory] - [MemberData(nameof(WhenSourceBufferIsShorterOrEqual_Data))] - public void WhenSourceBufferIsShorterOrEqual(int srcTotal, int srcBufLen, int trgTotal, int trgBufLen) - { - using MemoryGroup src = this.CreateTestGroup(srcTotal, srcBufLen, true); - using MemoryGroup trg = this.CreateTestGroup(trgTotal, trgBufLen, false); - - src.CopyTo(trg); - - MemoryGroupIndex i = src.MinIndex(); - MemoryGroupIndex j = trg.MinIndex(); - for (; i < src.MaxIndex(); i += 1, j += 1) - { - int a = src.GetElementAt(i); - int b = src.GetElementAt(j); - - Assert.Equal(a, b); - } - } - } - } -} diff --git a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs index f7865b00c..f9f725fb4 100644 --- a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs +++ b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Memory; using Xunit; @@ -26,6 +27,124 @@ namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers Assert.False(g.IsValid); } +#pragma warning disable SA1509 + private static readonly TheoryData CopyAndTransformData = + new TheoryData() + { + { 20, 10, 20, 10 }, + { 20, 5, 20, 4 }, + { 20, 4, 20, 5 }, + { 18, 6, 20, 5 }, + { 19, 10, 20, 10 }, + { 21, 10, 22, 2 }, + { 1, 5, 5, 4 }, + + { 30, 12, 40, 5 }, + { 30, 5, 40, 12 }, + }; + + public class CopyTo : MemoryGroupTestsBase + { + public static readonly TheoryData WhenSourceBufferIsShorterOrEqual_Data = + CopyAndTransformData; + + [Theory] + [MemberData(nameof(WhenSourceBufferIsShorterOrEqual_Data))] + public void WhenSourceBufferIsShorterOrEqual(int srcTotal, int srcBufLen, int trgTotal, int trgBufLen) + { + using MemoryGroup src = this.CreateTestGroup(srcTotal, srcBufLen, true); + using MemoryGroup trg = this.CreateTestGroup(trgTotal, trgBufLen, false); + + src.CopyTo(trg); + + int pos = 0; + MemoryGroupIndex i = src.MinIndex(); + MemoryGroupIndex j = trg.MinIndex(); + for (; i < src.MaxIndex(); i += 1, j += 1, pos++) + { + int a = src.GetElementAt(i); + int b = trg.GetElementAt(j); + + Assert.True(a == b, $"Mismatch @ {pos} Expected: {a} Actual: {b}"); + } + } + + [Fact] + public void WhenTargetBufferTooShort_Throws() + { + using MemoryGroup src = this.CreateTestGroup(10, 20, true); + using MemoryGroup trg = this.CreateTestGroup(5, 20, false); + + Assert.Throws(() => src.CopyTo(trg)); + } + } + + public class TransformTo : MemoryGroupTestsBase + { + public static readonly TheoryData WhenSourceBufferIsShorterOrEqual_Data = + CopyAndTransformData; + + [Theory] + [MemberData(nameof(WhenSourceBufferIsShorterOrEqual_Data))] + public void WhenSourceBufferIsShorterOrEqual(int srcTotal, int srcBufLen, int trgTotal, int trgBufLen) + { + using MemoryGroup src = this.CreateTestGroup(srcTotal, srcBufLen, true); + using MemoryGroup trg = this.CreateTestGroup(trgTotal, trgBufLen, false); + + src.TransformTo(trg, MultiplyAllBy2); + + int pos = 0; + MemoryGroupIndex i = src.MinIndex(); + MemoryGroupIndex j = trg.MinIndex(); + for (; i < src.MaxIndex(); i += 1, j += 1, pos++) + { + int a = src.GetElementAt(i); + int b = trg.GetElementAt(j); + + Assert.True(b == 2 * a, $"Mismatch @ {pos} Expected: {a} Actual: {b}"); + } + + + } + + [Fact] + public void WhenTargetBufferTooShort_Throws() + { + using MemoryGroup src = this.CreateTestGroup(10, 20, true); + using MemoryGroup trg = this.CreateTestGroup(5, 20, false); + + Assert.Throws(() => src.TransformTo(trg, MultiplyAllBy2)); + } + } + + + [Theory] + [InlineData(100, 5)] + [InlineData(100, 101)] + public void TransformInplace(int totalLength, int bufferLength) + { + using MemoryGroup src = this.CreateTestGroup(10, 20, true); + + src.TransformInplace(s => MultiplyAllBy2(s, s)); + + int cnt = 1; + for (MemoryGroupIndex i = src.MinIndex(); i < src.MaxIndex(); i += 1) + { + int val = src.GetElementAt(i); + Assert.Equal(expected: cnt * 2, val); + cnt++; + } + } + + private static void MultiplyAllBy2(ReadOnlySpan source, Span target) + { + Assert.Equal(source.Length, target.Length); + for (int k = 0; k < source.Length; k++) + { + target[k] = source[k] * 2; + } + } + [StructLayout(LayoutKind.Sequential, Size = 5)] private struct S5 { From 52f5f05243b8b15041fad0ae5cbb59defb5df284 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Mon, 3 Feb 2020 03:49:05 +0100 Subject: [PATCH 14/62] SwapOrCopyContent() works --- .../MemoryGroupView{T}.cs | 84 +++++++++++--- .../MemoryGroup{T}.Consumed.cs | 3 +- .../MemoryGroup{T}.Owned.cs | 39 +++++-- .../DiscontiguousBuffers/MemoryGroup{T}.cs | 66 ++++++++--- .../Memory/MemoryAllocatorExtensions.cs | 29 ++++- .../DiscontiguousBuffers/MemoryGroupIndex.cs | 9 +- .../MemoryGroupTests.Allocate.cs | 1 + .../MemoryGroupTests.SwapOrCopyContent.cs | 105 ++++++++++++++++++ .../MemoryGroupTests.View.cs | 84 ++++++++++++++ .../DiscontiguousBuffers/MemoryGroupTests.cs | 24 +++- .../MemoryGroupTestsBase.cs | 3 + 11 files changed, 392 insertions(+), 55 deletions(-) create mode 100644 tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.SwapOrCopyContent.cs create mode 100644 tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.View.cs diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupView{T}.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupView{T}.cs index ec801015a..3f39ba12f 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupView{T}.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupView{T}.cs @@ -21,12 +21,11 @@ namespace SixLabors.ImageSharp.Memory internal class MemoryGroupView : IMemoryGroup where T : struct { - private readonly Memory.MemoryGroup owner; + private MemoryGroup owner; private readonly MemoryOwnerWrapper[] memoryWrappers; - public MemoryGroupView(Memory.MemoryGroup owner) + public MemoryGroupView(MemoryGroup owner) { - this.IsValid = true; this.owner = owner; this.memoryWrappers = new MemoryOwnerWrapper[owner.Count]; @@ -36,20 +35,68 @@ namespace SixLabors.ImageSharp.Memory } } - public int Count => this.owner.Count; + public int Count + { + get + { + this.EnsureIsValid(); + return this.owner.Count; + } + } - public int BufferLength => this.owner.BufferLength; + public int BufferLength + { + get + { + this.EnsureIsValid(); + return this.owner.BufferLength; + } + } - public long TotalLength => this.owner.TotalLength; + public long TotalLength + { + get + { + this.EnsureIsValid(); + return this.owner.TotalLength; + } + } - public bool IsValid { get; internal set; } + public bool IsValid => this.owner != null; - public Memory this[int index] => throw new NotImplementedException(); + public Memory this[int index] + { + get + { + this.EnsureIsValid(); + return this.memoryWrappers[index].Memory; + } + } - public IEnumerator> GetEnumerator() => throw new NotImplementedException(); + public IEnumerator> GetEnumerator() + { + this.EnsureIsValid(); + for (int i = 0; i < this.Count; i++) + { + yield return this.memoryWrappers[i].Memory; + } + } IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); + internal void Invalidate() + { + this.owner = null; + } + + private void EnsureIsValid() + { + if (!this.IsValid) + { + throw new InvalidMemoryOperationException("Can not access an invalidated MemoryGroupView!"); + } + } + private class MemoryOwnerWrapper : MemoryManager { private readonly MemoryGroupView view; @@ -68,17 +115,20 @@ namespace SixLabors.ImageSharp.Memory public override Span GetSpan() { - if (!this.view.IsValid) - { - throw new InvalidOperationException(); - } - - return this.view[this.index].Span; + this.view.EnsureIsValid(); + return this.view.owner[this.index].Span; } - public override MemoryHandle Pin(int elementIndex = 0) => throw new NotImplementedException(); + public override MemoryHandle Pin(int elementIndex = 0) + { + this.view.EnsureIsValid(); + return this.view.owner[this.index].Pin(); + } - public override void Unpin() => throw new NotImplementedException(); + public override void Unpin() + { + throw new NotSupportedException(); + } } } } diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs index 4b7f8acae..b16692bd5 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs @@ -17,6 +17,7 @@ namespace SixLabors.ImageSharp.Memory : base(bufferLength, totalLength) { this.source = source; + this.View = new MemoryGroupView(this); } public override int Count => this.source.Length; @@ -33,7 +34,7 @@ namespace SixLabors.ImageSharp.Memory public override void Dispose() { - // No ownership nothing to 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 e0dfc6396..6f325d0b2 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs @@ -19,15 +19,9 @@ namespace SixLabors.ImageSharp.Memory : base(bufferLength, totalLength) { this.memoryOwners = memoryOwners; + this.View = new MemoryGroupView(this); } - public override IEnumerator> GetEnumerator() - { - this.EnsureNotDisposed(); - return this.memoryOwners.Select(mo => mo.Memory).GetEnumerator(); - } - - public override int Count { get @@ -46,6 +40,12 @@ namespace SixLabors.ImageSharp.Memory } } + public override IEnumerator> GetEnumerator() + { + this.EnsureNotDisposed(); + return this.memoryOwners.Select(mo => mo.Memory).GetEnumerator(); + } + public override void Dispose() { if (this.memoryOwners == null) @@ -53,6 +53,8 @@ namespace SixLabors.ImageSharp.Memory return; } + this.View.Invalidate(); + foreach (IMemoryOwner memoryOwner in this.memoryOwners) { memoryOwner.Dispose(); @@ -69,6 +71,29 @@ namespace SixLabors.ImageSharp.Memory 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; + + a.memoryOwners = b.memoryOwners; + a.TotalLength = b.TotalLength; + a.BufferLength = b.BufferLength; + + b.memoryOwners = tempOwners; + b.TotalLength = tempTotalLength; + b.BufferLength = tempBufferLength; + + a.View.Invalidate(); + b.View.Invalidate(); + a.View = new MemoryGroupView(a); + b.View = new MemoryGroupView(b); + } } } } diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs index 3883a111d..072b7e3e7 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs @@ -26,41 +26,60 @@ namespace SixLabors.ImageSharp.Memory this.TotalLength = totalLength; } + /// public abstract int Count { get; } - public int BufferLength { get; } + /// + public int BufferLength { get; private set; } - public long TotalLength { get; } + /// + public long TotalLength { get; private set; } + /// public bool IsValid { get; private set; } = true; + public MemoryGroupView View { get; private set; } + + /// public abstract Memory this[int index] { get; } + /// public abstract void Dispose(); + /// public abstract IEnumerator> GetEnumerator(); + /// IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); - // bufferLengthAlignment == image.Width in row-major images + /// + /// 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 . + /// 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 blockAlignment, - AllocationOptions allocationOptions = AllocationOptions.None) + int bufferAlignment, + AllocationOptions options = AllocationOptions.None) { Guard.NotNull(allocator, nameof(allocator)); Guard.MustBeGreaterThanOrEqualTo(totalLength, 0, nameof(totalLength)); - Guard.MustBeGreaterThan(blockAlignment, 0, nameof(blockAlignment)); + Guard.MustBeGreaterThan(bufferAlignment, 0, nameof(bufferAlignment)); int blockCapacityInElements = allocator.GetBufferCapacityInBytes() / ElementSize; - if (blockAlignment > blockCapacityInElements) + if (bufferAlignment > blockCapacityInElements) { - throw new InvalidMemoryOperationException(); + throw new InvalidMemoryOperationException( + $"The buffer capacity of the provided MemoryAllocator is insufficient for the requested buffer alignment: {bufferAlignment}."); } - int numberOfAlignedSegments = blockCapacityInElements / blockAlignment; - int bufferLength = numberOfAlignedSegments * blockAlignment; + int numberOfAlignedSegments = blockCapacityInElements / bufferAlignment; + int bufferLength = numberOfAlignedSegments * bufferAlignment; if (totalLength > 0 && totalLength < bufferLength) { bufferLength = (int)totalLength; @@ -81,12 +100,12 @@ namespace SixLabors.ImageSharp.Memory var buffers = new IMemoryOwner[bufferCount]; for (int i = 0; i < buffers.Length - 1; i++) { - buffers[i] = allocator.Allocate(bufferLength, allocationOptions); + buffers[i] = allocator.Allocate(bufferLength, options); } if (bufferCount > 0) { - buffers[^1] = allocator.Allocate(sizeOfLastBuffer, allocationOptions); + buffers[^1] = allocator.Allocate(sizeOfLastBuffer, options); } return new Owned(buffers, bufferLength, totalLength); @@ -115,10 +134,27 @@ namespace SixLabors.ImageSharp.Memory return new Consumed(source, bufferLength, totalLength); } - // Analogous to current MemorySource.SwapOrCopyContent() - public static void SwapOrCopyContent(MemoryGroup destination, MemoryGroup source) + /// + /// 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 void SwapOrCopyContent(MemoryGroup target, MemoryGroup source) { - throw new NotImplementedException(); + if (source is Owned ownedSrc && target is Owned ownedTarget) + { + Owned.SwapContents(ownedTarget, ownedSrc); + } + 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."); + } + + source.CopyTo(target); + } } } } diff --git a/src/ImageSharp/Memory/MemoryAllocatorExtensions.cs b/src/ImageSharp/Memory/MemoryAllocatorExtensions.cs index 6e317bb8f..b9a0d2536 100644 --- a/src/ImageSharp/Memory/MemoryAllocatorExtensions.cs +++ b/src/ImageSharp/Memory/MemoryAllocatorExtensions.cs @@ -17,7 +17,7 @@ namespace SixLabors.ImageSharp.Memory /// The type of buffer items to allocate. /// The memory allocator. /// The buffer width. - /// The buffer heght. + /// The buffer height. /// The allocation options. /// The . public static Buffer2D Allocate2D( @@ -50,13 +50,13 @@ namespace SixLabors.ImageSharp.Memory Allocate2D(memoryAllocator, size.Width, size.Height, options); /// - /// Allocates padded buffers for BMP encoder/decoder. (Replacing old PixelRow/PixelArea) + /// Allocates padded buffers for BMP encoder/decoder. (Replacing old PixelRow/PixelArea). /// - /// The + /// The . /// Pixel count in the row - /// The pixel size in bytes, eg. 3 for RGB - /// The padding - /// A + /// The pixel size in bytes, eg. 3 for RGB. + /// The padding. + /// A . internal static IManagedByteBuffer AllocatePaddedPixelRowBuffer( this MemoryAllocator memoryAllocator, int width, @@ -66,5 +66,22 @@ namespace SixLabors.ImageSharp.Memory int length = (width * pixelSizeInBytes) + paddingInBytes; return memoryAllocator.AllocateManagedByteBuffer(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/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupIndex.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupIndex.cs index d619aec29..88824baf2 100644 --- a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupIndex.cs +++ b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupIndex.cs @@ -112,12 +112,9 @@ namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers public static MemoryGroupIndex MaxIndex(this MemoryGroup group) where T : struct { - if (group.Count == 0) - { - return new MemoryGroupIndex(group.BufferLength, 0, 0); - } - - return new MemoryGroupIndex(group.BufferLength, group.Count - 1, group[^1].Length - 1); + return group.Count == 0 + ? new MemoryGroupIndex(group.BufferLength, 0, 0) + : new MemoryGroupIndex(group.BufferLength, group.Count - 1, group[^1].Length); } } } diff --git a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.Allocate.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.Allocate.cs index 0c96f3d78..972f6cb26 100644 --- a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.Allocate.cs +++ b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.Allocate.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System; using System.Collections.Generic; using System.Linq; using SixLabors.ImageSharp.Memory; diff --git a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.SwapOrCopyContent.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.SwapOrCopyContent.cs new file mode 100644 index 000000000..b3a522cc6 --- /dev/null +++ b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.SwapOrCopyContent.cs @@ -0,0 +1,105 @@ +// Copyright (c) Six Labors and contributors. +// 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]; + + MemoryGroup.SwapOrCopyContent(a, b); + + 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: + MemoryGroup.SwapOrCopyContent(dest, source); + + // Assert: + 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]); + } + } + } +} diff --git a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.View.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.View.cs new file mode 100644 index 000000000..8884037a5 --- /dev/null +++ b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.View.cs @@ -0,0 +1,84 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.Memory; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers +{ + public partial class MemoryGroupTests + { + public class View : MemoryGroupTestsBase + { + [Fact] + public void RefersToOwnerGroupContent() + { + using MemoryGroup group = this.CreateTestGroup(240, 80, true); + + MemoryGroupView view = group.View; + Assert.True(view.IsValid); + Assert.Equal(group.Count, view.Count); + Assert.Equal(group.BufferLength, view.BufferLength); + Assert.Equal(group.TotalLength, view.TotalLength); + int cnt = 1; + foreach (Memory memory in view) + { + Span span = memory.Span; + foreach (int t in span) + { + Assert.Equal(cnt, t); + cnt++; + } + } + } + + [Fact] + public void IsInvalidatedOnOwnerGroupDispose() + { + MemoryGroupView view; + using (MemoryGroup group = this.CreateTestGroup(240, 80, true)) + { + view = group.View; + } + + Assert.False(view.IsValid); + + Assert.ThrowsAny(() => + { + _ = view.Count; + }); + + Assert.ThrowsAny(() => + { + _ = view.BufferLength; + }); + + Assert.ThrowsAny(() => + { + _ = view.TotalLength; + }); + + Assert.ThrowsAny(() => + { + _ = view[0]; + }); + } + + [Fact] + public void WhenInvalid_CanNotUseMemberMemory() + { + Memory memory; + using (MemoryGroup group = this.CreateTestGroup(240, 80, true)) + { + memory = group.View[0]; + } + + Assert.ThrowsAny(() => + { + _ = memory.Span; + }); + } + } + } +} diff --git a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs index f9f725fb4..6b0737742 100644 --- a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs +++ b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Buffers; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Memory; using Xunit; @@ -103,8 +104,6 @@ namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers Assert.True(b == 2 * a, $"Mismatch @ {pos} Expected: {a} Actual: {b}"); } - - } [Fact] @@ -117,7 +116,6 @@ namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers } } - [Theory] [InlineData(100, 5)] [InlineData(100, 101)] @@ -136,6 +134,26 @@ namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers } } + [Fact] + public void Wrap() + { + int[] data0 = { 1, 2, 3, 4 }; + int[] data1 = { 5, 6, 7, 8 }; + int[] data2 = { 9, 10 }; + using var mgr0 = new TestMemoryManager(data0); + using var mgr1 = new TestMemoryManager(data1); + + using var group = MemoryGroup.Wrap(mgr0.Memory, mgr1.Memory, data2); + + Assert.Equal(3, group.Count); + Assert.Equal(4, group.BufferLength); + Assert.Equal(10, group.TotalLength); + + Assert.True(group[0].Span.SequenceEqual(data0)); + Assert.True(group[1].Span.SequenceEqual(data1)); + Assert.True(group[2].Span.SequenceEqual(data2)); + } + private static void MultiplyAllBy2(ReadOnlySpan source, Span target) { Assert.Equal(source.Length, target.Length); diff --git a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTestsBase.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTestsBase.cs index acd24e343..8dd28653c 100644 --- a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTestsBase.cs +++ b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTestsBase.cs @@ -9,6 +9,9 @@ namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers { internal readonly TestMemoryAllocator MemoryAllocator = new TestMemoryAllocator(); + /// + /// Create a group, either uninitialized or filled with incrementing numbers starting with 1. + /// internal MemoryGroup CreateTestGroup(long totalLength, int bufferLength, bool fillSequence = false) { this.MemoryAllocator.BufferCapacityInBytes = bufferLength * sizeof(int); From eb8ea992f58389030f8a82c7470c0d82f4e8e456 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Tue, 4 Feb 2020 00:14:27 +0100 Subject: [PATCH 15/62] GetBoundedSlice, CopyTo/From span --- .../MemoryGroupExtensions.cs | 70 +++++++++++ .../MemoryGroupTests.CopyTo.cs | 111 ++++++++++++++++++ .../DiscontiguousBuffers/MemoryGroupTests.cs | 81 +++++++------ 3 files changed, 226 insertions(+), 36 deletions(-) create mode 100644 tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.CopyTo.cs diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupExtensions.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupExtensions.cs index 14e676dbe..ce719dc91 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupExtensions.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupExtensions.cs @@ -7,6 +7,76 @@ namespace SixLabors.ImageSharp.Memory { internal static class MemoryGroupExtensions { + /// + /// Returns a slice that is expected to be within the bounds of a single buffer. + /// Otherwise is thrown. + /// + public static Memory GetBoundedSlice(this IMemoryGroup group, long start, int length) + where T : struct + { + Guard.NotNull(group, nameof(group)); + Guard.IsTrue(group.IsValid, nameof(group), "Group must be valid!"); + Guard.MustBeGreaterThanOrEqualTo(length, 0, nameof(length)); + Guard.MustBeLessThan(start, group.TotalLength, nameof(start)); + + int bufferIdx = (int)(start / group.BufferLength); + if (bufferIdx >= group.Count) + { + throw new ArgumentOutOfRangeException(nameof(start)); + } + + int bufferStart = (int)(start % group.BufferLength); + int bufferEnd = bufferStart + length; + Memory memory = group[bufferIdx]; + + if (bufferEnd > memory.Length) + { + throw new ArgumentOutOfRangeException(nameof(length)); + } + + return memory.Slice(bufferStart, length); + } + + public static void CopyTo(this IMemoryGroup source, Span target) + where T : struct + { + Guard.NotNull(source, nameof(source)); + Guard.MustBeGreaterThanOrEqualTo(target.Length, source.TotalLength, nameof(target)); + + var cur = new MemoryGroupCursor(source); + long position = 0; + while (position < source.TotalLength) + { + int fwd = Math.Min(cur.LookAhead(), target.Length); + cur.GetSpan(fwd).CopyTo(target); + + cur.Forward(fwd); + target = target.Slice(fwd); + position += fwd; + } + } + + public static void CopyTo(this Span source, IMemoryGroup target) + where T : struct + => CopyTo((ReadOnlySpan)source, target); + + public static void CopyTo(this ReadOnlySpan source, IMemoryGroup target) + where T : struct + { + Guard.NotNull(target, nameof(target)); + Guard.MustBeGreaterThanOrEqualTo(target.TotalLength, source.Length, nameof(target)); + + var cur = new MemoryGroupCursor(target); + + while (!source.IsEmpty) + { + int fwd = Math.Min(cur.LookAhead(), source.Length); + source.Slice(0, fwd).CopyTo(cur.GetSpan(fwd)); + cur.Forward(fwd); + source = source.Slice(fwd); + } + } + public static void CopyTo(this IMemoryGroup source, IMemoryGroup target) where T : struct { diff --git a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.CopyTo.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.CopyTo.cs new file mode 100644 index 000000000..ab69a3077 --- /dev/null +++ b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.CopyTo.cs @@ -0,0 +1,111 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.Memory; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers +{ + public partial class MemoryGroupTests + { + public class CopyTo : MemoryGroupTestsBase + { + public static readonly TheoryData WhenSourceBufferIsShorterOrEqual_Data = + CopyAndTransformData; + + [Theory] + [MemberData(nameof(WhenSourceBufferIsShorterOrEqual_Data))] + public void WhenSourceBufferIsShorterOrEqual(int srcTotal, int srcBufLen, int trgTotal, int trgBufLen) + { + using MemoryGroup src = this.CreateTestGroup(srcTotal, srcBufLen, true); + using MemoryGroup trg = this.CreateTestGroup(trgTotal, trgBufLen, false); + + src.CopyTo(trg); + + int pos = 0; + MemoryGroupIndex i = src.MinIndex(); + MemoryGroupIndex j = trg.MinIndex(); + for (; i < src.MaxIndex(); i += 1, j += 1, pos++) + { + int a = src.GetElementAt(i); + int b = trg.GetElementAt(j); + + Assert.True(a == b, $"Mismatch @ {pos} Expected: {a} Actual: {b}"); + } + } + + [Fact] + public void WhenTargetBufferTooShort_Throws() + { + using MemoryGroup src = this.CreateTestGroup(10, 20, true); + using MemoryGroup trg = this.CreateTestGroup(5, 20, false); + + Assert.Throws(() => src.CopyTo(trg)); + } + + [Theory] + [InlineData(30, 10, 40)] + [InlineData(42, 23, 42)] + [InlineData(1, 3, 10)] + [InlineData(0, 4, 0)] + public void GroupToSpan_Success(long totalLength, int bufferLength, int spanLength) + { + using MemoryGroup src = this.CreateTestGroup(totalLength, bufferLength, true); + var trg = new int[spanLength]; + src.CopyTo(trg); + + int expected = 1; + foreach (int val in trg.AsSpan().Slice(0, (int)totalLength)) + { + Assert.Equal(expected, val); + expected++; + } + } + + [Theory] + [InlineData(20, 7, 19)] + [InlineData(2, 1, 1)] + public void GroupToSpan_OutOfRange(long totalLength, int bufferLength, int spanLength) + { + using MemoryGroup src = this.CreateTestGroup(totalLength, bufferLength, true); + var trg = new int[spanLength]; + Assert.ThrowsAny(() => src.CopyTo(trg)); + } + + [Theory] + [InlineData(30, 35, 10)] + [InlineData(42, 23, 42)] + [InlineData(10, 3, 1)] + [InlineData(0, 3, 0)] + public void SpanToGroup_Success(long totalLength, int bufferLength, int spanLength) + { + var src = new int[spanLength]; + for (int i = 0; i < src.Length; i++) + { + src[i] = i + 1; + } + + using MemoryGroup trg = this.CreateTestGroup(totalLength, bufferLength); + src.AsSpan().CopyTo(trg); + + int position = 0; + for (MemoryGroupIndex i = trg.MinIndex(); position < spanLength; i += 1, position++) + { + int expected = position + 1; + Assert.Equal(expected, trg.GetElementAt(i)); + } + } + + [Theory] + [InlineData(10, 3, 11)] + [InlineData(0, 3, 1)] + public void SpanToGroup_OutOfRange(long totalLength, int bufferLength, int spanLength) + { + var src = new int[spanLength]; + using MemoryGroup trg = this.CreateTestGroup(totalLength, bufferLength, true); + Assert.ThrowsAny(() => src.AsSpan().CopyTo(trg)); + } + } + } +} diff --git a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs index 6b0737742..bf081cb55 100644 --- a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs +++ b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs @@ -44,42 +44,6 @@ namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers { 30, 5, 40, 12 }, }; - public class CopyTo : MemoryGroupTestsBase - { - public static readonly TheoryData WhenSourceBufferIsShorterOrEqual_Data = - CopyAndTransformData; - - [Theory] - [MemberData(nameof(WhenSourceBufferIsShorterOrEqual_Data))] - public void WhenSourceBufferIsShorterOrEqual(int srcTotal, int srcBufLen, int trgTotal, int trgBufLen) - { - using MemoryGroup src = this.CreateTestGroup(srcTotal, srcBufLen, true); - using MemoryGroup trg = this.CreateTestGroup(trgTotal, trgBufLen, false); - - src.CopyTo(trg); - - int pos = 0; - MemoryGroupIndex i = src.MinIndex(); - MemoryGroupIndex j = trg.MinIndex(); - for (; i < src.MaxIndex(); i += 1, j += 1, pos++) - { - int a = src.GetElementAt(i); - int b = trg.GetElementAt(j); - - Assert.True(a == b, $"Mismatch @ {pos} Expected: {a} Actual: {b}"); - } - } - - [Fact] - public void WhenTargetBufferTooShort_Throws() - { - using MemoryGroup src = this.CreateTestGroup(10, 20, true); - using MemoryGroup trg = this.CreateTestGroup(5, 20, false); - - Assert.Throws(() => src.CopyTo(trg)); - } - } - public class TransformTo : MemoryGroupTestsBase { public static readonly TheoryData WhenSourceBufferIsShorterOrEqual_Data = @@ -154,6 +118,51 @@ namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers Assert.True(group[2].Span.SequenceEqual(data2)); } + public static TheoryData GetBoundedSlice_SuccessData = new TheoryData() + { + { 300, 100, 110, 80 }, + { 300, 100, 100, 100 }, + { 280, 100, 201, 79 }, + { 42, 7, 0, 0 }, + { 42, 7, 0, 1 }, + { 42, 7, 0, 7 }, + { 42, 9, 9, 9 }, + }; + + [Theory] + [MemberData(nameof(GetBoundedSlice_SuccessData))] + public void GetBoundedSlice_WhenArgsAreCorrect(long totalLength, int bufferLength, long start, int length) + { + using MemoryGroup group = this.CreateTestGroup(totalLength, bufferLength, true); + + Memory slice = group.GetBoundedSlice(start, length); + + Assert.Equal(length, slice.Length); + + int expected = (int)start + 1; + foreach (int val in slice.Span) + { + Assert.Equal(expected, val); + expected++; + } + } + + public static TheoryData GetBoundedSlice_ErrorData = new TheoryData() + { + { 300, 100, 110, 91 }, + { 42, 7, 0, 8 }, + { 42, 7, 1, 7 }, + { 42, 7, 1, 30 }, + }; + + [Theory] + [MemberData(nameof(GetBoundedSlice_ErrorData))] + 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)); + } + private static void MultiplyAllBy2(ReadOnlySpan source, Span target) { Assert.Equal(source.Length, target.Length); From a4980be72d271cf84fa1fb7491019cda22abf586 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Tue, 4 Feb 2020 01:56:03 +0100 Subject: [PATCH 16/62] replace MemorySource with MemoryGroup --- shared-infrastructure | 2 +- .../Advanced/AdvancedImageExtensions.cs | 2 +- src/ImageSharp/Image.Decode.cs | 2 +- src/ImageSharp/Image.WrapMemory.cs | 6 +- .../ImageFrameCollection{TPixel}.cs | 2 +- src/ImageSharp/ImageFrame{TPixel}.cs | 4 +- src/ImageSharp/Image{TPixel}.cs | 8 +- src/ImageSharp/Memory/Buffer2DExtensions.cs | 10 +- src/ImageSharp/Memory/Buffer2D{T}.cs | 24 ++- .../MemoryGroup{T}.Consumed.cs | 10 +- .../MemoryGroup{T}.Owned.cs | 5 +- .../DiscontiguousBuffers/MemoryGroup{T}.cs | 38 ++++- .../Memory/MemoryAllocatorExtensions.cs | 5 +- src/ImageSharp/Memory/MemorySource.cs | 101 ----------- .../Formats/Jpg/JpegColorConverterTests.cs | 2 +- .../Formats/Jpg/SpectralJpegTests.cs | 3 +- .../ImageSharp.Tests/Memory/Buffer2DTests.cs | 67 +++++--- .../Memory/MemorySourceTests.cs | 159 ------------------ 18 files changed, 117 insertions(+), 333 deletions(-) delete mode 100644 src/ImageSharp/Memory/MemorySource.cs delete mode 100644 tests/ImageSharp.Tests/Memory/MemorySourceTests.cs diff --git a/shared-infrastructure b/shared-infrastructure index a75469fdb..36b2d55f5 160000 --- a/shared-infrastructure +++ b/shared-infrastructure @@ -1 +1 @@ -Subproject commit a75469fdb93fb89b39a5b0b7c01cb7432ceef98f +Subproject commit 36b2d55f5bb0d91024955bd26ba220ee41cc96e5 diff --git a/src/ImageSharp/Advanced/AdvancedImageExtensions.cs b/src/ImageSharp/Advanced/AdvancedImageExtensions.cs index d810296d6..cfaffbbb2 100644 --- a/src/ImageSharp/Advanced/AdvancedImageExtensions.cs +++ b/src/ImageSharp/Advanced/AdvancedImageExtensions.cs @@ -129,7 +129,7 @@ namespace SixLabors.ImageSharp.Advanced internal static Memory GetPixelMemory(this ImageFrame source) where TPixel : struct, IPixel { - return source.PixelBuffer.MemorySource.Memory; + return source.PixelBuffer.GetMemory(); } /// diff --git a/src/ImageSharp/Image.Decode.cs b/src/ImageSharp/Image.Decode.cs index e1376b4a2..e6bcae45c 100644 --- a/src/ImageSharp/Image.Decode.cs +++ b/src/ImageSharp/Image.Decode.cs @@ -36,7 +36,7 @@ namespace SixLabors.ImageSharp { Buffer2D uninitializedMemoryBuffer = configuration.MemoryAllocator.Allocate2D(width, height); - return new Image(configuration, uninitializedMemoryBuffer.MemorySource, width, height, metadata); + return new Image(configuration, uninitializedMemoryBuffer.MemoryGroup, width, height, metadata); } /// diff --git a/src/ImageSharp/Image.WrapMemory.cs b/src/ImageSharp/Image.WrapMemory.cs index 095991b07..9bb40a78b 100644 --- a/src/ImageSharp/Image.WrapMemory.cs +++ b/src/ImageSharp/Image.WrapMemory.cs @@ -34,7 +34,7 @@ namespace SixLabors.ImageSharp ImageMetadata metadata) where TPixel : struct, IPixel { - var memorySource = new MemorySource(pixelMemory); + var memorySource = MemoryGroup.Wrap(pixelMemory); return new Image(config, memorySource, width, height, metadata); } @@ -99,7 +99,7 @@ namespace SixLabors.ImageSharp ImageMetadata metadata) where TPixel : struct, IPixel { - var memorySource = new MemorySource(pixelMemoryOwner, false); + var memorySource = MemoryGroup.Wrap(pixelMemoryOwner); return new Image(config, memorySource, width, height, metadata); } @@ -147,4 +147,4 @@ namespace SixLabors.ImageSharp return WrapMemory(Configuration.Default, pixelMemoryOwner, width, height); } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/ImageFrameCollection{TPixel}.cs b/src/ImageSharp/ImageFrameCollection{TPixel}.cs index 722a4ddea..b11c74958 100644 --- a/src/ImageSharp/ImageFrameCollection{TPixel}.cs +++ b/src/ImageSharp/ImageFrameCollection{TPixel}.cs @@ -30,7 +30,7 @@ namespace SixLabors.ImageSharp this.frames.Add(new ImageFrame(parent.GetConfiguration(), width, height, backgroundColor)); } - internal ImageFrameCollection(Image parent, int width, int height, MemorySource memorySource) + internal ImageFrameCollection(Image parent, int width, int height, MemoryGroup memorySource) { this.parent = parent ?? throw new ArgumentNullException(nameof(parent)); diff --git a/src/ImageSharp/ImageFrame{TPixel}.cs b/src/ImageSharp/ImageFrame{TPixel}.cs index e1112c017..421cd1032 100644 --- a/src/ImageSharp/ImageFrame{TPixel}.cs +++ b/src/ImageSharp/ImageFrame{TPixel}.cs @@ -99,7 +99,7 @@ namespace SixLabors.ImageSharp /// The width of the image in pixels. /// The height of the image in pixels. /// The memory source. - internal ImageFrame(Configuration configuration, int width, int height, MemorySource memorySource) + internal ImageFrame(Configuration configuration, int width, int height, MemoryGroup memorySource) : this(configuration, width, height, memorySource, new ImageFrameMetadata()) { } @@ -112,7 +112,7 @@ namespace SixLabors.ImageSharp /// The height of the image in pixels. /// The memory source. /// The metadata. - internal ImageFrame(Configuration configuration, int width, int height, MemorySource memorySource, ImageFrameMetadata metadata) + internal ImageFrame(Configuration configuration, int width, int height, MemoryGroup memorySource, ImageFrameMetadata metadata) : base(configuration, width, height, metadata) { Guard.MustBeGreaterThan(width, 0, nameof(width)); diff --git a/src/ImageSharp/Image{TPixel}.cs b/src/ImageSharp/Image{TPixel}.cs index 87bdf90a1..c60f6638c 100644 --- a/src/ImageSharp/Image{TPixel}.cs +++ b/src/ImageSharp/Image{TPixel}.cs @@ -74,22 +74,22 @@ namespace SixLabors.ImageSharp /// /// Initializes a new instance of the class - /// wrapping an external . + /// wrapping an external . /// /// The configuration providing initialization code which allows extending the library. - /// The memory source. + /// The memory source. /// The width of the image in pixels. /// The height of the image in pixels. /// The images metadata. internal Image( Configuration configuration, - MemorySource memorySource, + MemoryGroup memoryGroup, int width, int height, ImageMetadata metadata) : base(configuration, PixelTypeInfo.Create(), metadata, width, height) { - this.Frames = new ImageFrameCollection(this, width, height, memorySource); + this.Frames = new ImageFrameCollection(this, width, height, memoryGroup); } /// diff --git a/src/ImageSharp/Memory/Buffer2DExtensions.cs b/src/ImageSharp/Memory/Buffer2DExtensions.cs index ba4f9c925..959ad94bb 100644 --- a/src/ImageSharp/Memory/Buffer2DExtensions.cs +++ b/src/ImageSharp/Memory/Buffer2DExtensions.cs @@ -3,6 +3,7 @@ using System; using System.Diagnostics; +using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -23,7 +24,7 @@ namespace SixLabors.ImageSharp.Memory where T : struct { Guard.NotNull(buffer, nameof(buffer)); - return buffer.MemorySource.GetSpan(); + return buffer.MemoryGroup.Single().Span; } /// @@ -36,7 +37,7 @@ namespace SixLabors.ImageSharp.Memory where T : struct { Guard.NotNull(buffer, nameof(buffer)); - return buffer.MemorySource.Memory; + return buffer.MemoryGroup.Single(); } /// @@ -51,7 +52,7 @@ namespace SixLabors.ImageSharp.Memory where T : struct { Guard.NotNull(buffer, nameof(buffer)); - return buffer.GetSpan().Slice(y * buffer.Width, buffer.Width); + return buffer.MemoryGroup.GetBoundedSlice(y * buffer.Width, buffer.Width).Span; } /// @@ -66,10 +67,11 @@ namespace SixLabors.ImageSharp.Memory where T : struct { Guard.NotNull(buffer, nameof(buffer)); - return buffer.MemorySource.Memory.Slice(y * buffer.Width, buffer.Width); + return buffer.MemoryGroup.GetBoundedSlice(y * buffer.Width, buffer.Width); } /// + /// TODO: Does not work with multi-buffer groups, should be specific to Resize. /// Copy columns of inplace, /// from positions starting at to positions at . /// diff --git a/src/ImageSharp/Memory/Buffer2D{T}.cs b/src/ImageSharp/Memory/Buffer2D{T}.cs index 6b7f3bf42..439a5bde2 100644 --- a/src/ImageSharp/Memory/Buffer2D{T}.cs +++ b/src/ImageSharp/Memory/Buffer2D{T}.cs @@ -14,21 +14,18 @@ namespace SixLabors.ImageSharp.Memory /// Before RC1, this class might be target of API changes, use it on your own risk! /// /// The value type. - // TODO: Consider moving this type to the SixLabors.ImageSharp.Memory namespace (SixLabors.Core). public sealed class Buffer2D : IDisposable where T : struct { - private MemorySource memorySource; - /// /// Initializes a new instance of the class. /// - /// The buffer to wrap - /// The number of elements in a row - /// The number of rows - internal Buffer2D(MemorySource memorySource, int width, int height) + /// The to wrap. + /// The number of elements in a row. + /// The number of rows. + internal Buffer2D(MemoryGroup memoryGroup, int width, int height) { - this.memorySource = memorySource; + this.MemoryGroup = memoryGroup; this.Width = width; this.Height = height; } @@ -44,9 +41,9 @@ namespace SixLabors.ImageSharp.Memory public int Height { get; private set; } /// - /// Gets the backing + /// Gets the backing . /// - internal MemorySource MemorySource => this.memorySource; + internal MemoryGroup MemoryGroup { get; } /// /// Gets a reference to the element at the specified position. @@ -62,8 +59,7 @@ namespace SixLabors.ImageSharp.Memory DebugGuard.MustBeLessThan(x, this.Width, nameof(x)); DebugGuard.MustBeLessThan(y, this.Height, nameof(y)); - Span span = this.GetSpan(); - return ref span[(this.Width * y) + x]; + return ref this.GetRowSpan(y)[x]; } } @@ -72,7 +68,7 @@ namespace SixLabors.ImageSharp.Memory /// public void Dispose() { - this.MemorySource.Dispose(); + this.MemoryGroup.Dispose(); } /// @@ -81,7 +77,7 @@ namespace SixLabors.ImageSharp.Memory /// internal static void SwapOrCopyContent(Buffer2D destination, Buffer2D source) { - MemorySource.SwapOrCopyContent(ref destination.memorySource, ref source.memorySource); + MemoryGroup.SwapOrCopyContent(destination.MemoryGroup, source.MemoryGroup); SwapDimensionData(destination, source); } diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs index b16692bd5..50987d2cd 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs @@ -2,7 +2,9 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Buffers; using System.Collections.Generic; +using System.Linq; namespace SixLabors.ImageSharp.Memory { @@ -11,9 +13,9 @@ namespace SixLabors.ImageSharp.Memory // Analogous to the "consumed" variant of MemorySource private class Consumed : MemoryGroup { - private readonly ReadOnlyMemory> source; + private readonly Memory[] source; - public Consumed(ReadOnlyMemory> source, int bufferLength, long totalLength) + public Consumed(Memory[] source, int bufferLength, long totalLength) : base(bufferLength, totalLength) { this.source = source; @@ -22,13 +24,13 @@ namespace SixLabors.ImageSharp.Memory public override int Count => this.source.Length; - public override Memory this[int index] => this.source.Span[index]; + public override Memory this[int index] => this.source[index]; public override IEnumerator> GetEnumerator() { for (int i = 0; i < this.source.Length; i++) { - yield return this.source.Span[i]; + yield return this.source[i]; } } diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs index 6f325d0b2..d7d484ceb 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs @@ -15,13 +15,16 @@ namespace SixLabors.ImageSharp.Memory { private IMemoryOwner[] memoryOwners; - public Owned(IMemoryOwner[] memoryOwners, int bufferLength, long totalLength) + public Owned(IMemoryOwner[] memoryOwners, int bufferLength, long totalLength, bool swappable) : base(bufferLength, totalLength) { this.memoryOwners = memoryOwners; + this.Swappable = swappable; this.View = new MemoryGroupView(this); } + public bool Swappable { get; } + public override int Count { get diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs index 072b7e3e7..d9c384b55 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs @@ -108,32 +108,51 @@ namespace SixLabors.ImageSharp.Memory buffers[^1] = allocator.Allocate(sizeOfLastBuffer, options); } - return new Owned(buffers, bufferLength, totalLength); + return new Owned(buffers, bufferLength, totalLength, true); } - public static MemoryGroup Wrap(params Memory[] source) => Wrap(source.AsMemory()); - - public static MemoryGroup Wrap(ReadOnlyMemory> source) + public static MemoryGroup Wrap(params Memory[] source) { - int bufferLength = source.Length > 0 ? source.Span[0].Length : 0; + int bufferLength = source.Length > 0 ? source[0].Length : 0; for (int i = 1; i < source.Length - 1; i++) { - if (source.Span[i].Length != bufferLength) + if (source[i].Length != bufferLength) { throw new InvalidMemoryOperationException("Wrap: buffers should be uniformly sized!"); } } - if (source.Length > 0 && source.Span[^1].Length > bufferLength) + if (source.Length > 0 && source[^1].Length > bufferLength) { throw new InvalidMemoryOperationException("Wrap: the last buffer is too large!"); } - long totalLength = bufferLength > 0 ? ((long)bufferLength * (source.Length - 1)) + source.Span[^1].Length : 0; + long totalLength = bufferLength > 0 ? ((long)bufferLength * (source.Length - 1)) + source[^1].Length : 0; return new Consumed(source, bufferLength, totalLength); } + public static MemoryGroup Wrap(params IMemoryOwner[] source) + { + int bufferLength = source.Length > 0 ? source[0].Memory.Length : 0; + for (int i = 1; i < source.Length - 1; i++) + { + if (source[i].Memory.Length != bufferLength) + { + throw new InvalidMemoryOperationException("Wrap: buffers should be uniformly sized!"); + } + } + + if (source.Length > 0 && source[^1].Memory.Length > bufferLength) + { + throw new InvalidMemoryOperationException("Wrap: the last buffer is too large!"); + } + + long totalLength = bufferLength > 0 ? ((long)bufferLength * (source.Length - 1)) + source[^1].Memory.Length : 0; + + 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). @@ -141,7 +160,8 @@ namespace SixLabors.ImageSharp.Memory /// public static void SwapOrCopyContent(MemoryGroup target, MemoryGroup source) { - if (source is Owned ownedSrc && target is Owned ownedTarget) + if (source is Owned ownedSrc && ownedSrc.Swappable && + target is Owned ownedTarget && ownedTarget.Swappable) { Owned.SwapContents(ownedTarget, ownedSrc); } diff --git a/src/ImageSharp/Memory/MemoryAllocatorExtensions.cs b/src/ImageSharp/Memory/MemoryAllocatorExtensions.cs index b9a0d2536..1f1ded9a0 100644 --- a/src/ImageSharp/Memory/MemoryAllocatorExtensions.cs +++ b/src/ImageSharp/Memory/MemoryAllocatorExtensions.cs @@ -27,9 +27,8 @@ namespace SixLabors.ImageSharp.Memory AllocationOptions options = AllocationOptions.None) where T : struct { - IMemoryOwner buffer = memoryAllocator.Allocate(width * height, options); - var memorySource = new MemorySource(buffer, true); - + long groupLength = (long)width * height; + MemoryGroup memorySource = memoryAllocator.AllocateGroup(groupLength, width, options); return new Buffer2D(memorySource, width, height); } diff --git a/src/ImageSharp/Memory/MemorySource.cs b/src/ImageSharp/Memory/MemorySource.cs deleted file mode 100644 index 54f1bb0d1..000000000 --- a/src/ImageSharp/Memory/MemorySource.cs +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Buffers; - -namespace SixLabors.ImageSharp.Memory -{ - /// - /// Holds a that is either OWNED or CONSUMED. - /// When the memory is being owned, the instance is also known. - /// Implements content transfer logic in that depends on the ownership status. - /// This is needed to transfer the contents of a temporary - /// to a persistent without copying the buffer. - /// - /// - /// For a deeper understanding of the owner/consumer model, check out the following docs:
- /// https://gist.github.com/GrabYourPitchforks/4c3e1935fd4d9fa2831dbfcab35dffc6 - /// https://www.codemag.com/Article/1807051/Introducing-.NET-Core-2.1-Flagship-Types-Span-T-and-Memory-T - ///
- internal struct MemorySource : IDisposable - { - /// - /// Initializes a new instance of the struct - /// by wrapping an existing . - /// - /// The to wrap - /// - /// A value indicating whether is an internal memory source managed by ImageSharp. - /// Eg. allocated by a . - /// - public MemorySource(IMemoryOwner memoryOwner, bool isInternalMemorySource) - { - this.MemoryOwner = memoryOwner; - this.Memory = memoryOwner.Memory; - this.HasSwappableContents = isInternalMemorySource; - } - - public MemorySource(Memory memory) - { - this.Memory = memory; - this.MemoryOwner = null; - this.HasSwappableContents = false; - } - - public IMemoryOwner MemoryOwner { get; private set; } - - public Memory Memory { get; private set; } - - /// - /// Gets a value indicating whether we are allowed to swap the contents of this buffer - /// with an other instance. - /// The value is true only and only if is present, - /// and it's coming from an internal source managed by ImageSharp (). - /// - public bool HasSwappableContents { get; } - - public Span GetSpan() => this.Memory.Span; - - public void Clear() => this.Memory.Span.Clear(); - - /// - /// 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! - /// - public static void SwapOrCopyContent(ref MemorySource destination, ref MemorySource source) - { - if (source.HasSwappableContents && destination.HasSwappableContents) - { - SwapContents(ref destination, ref source); - } - else - { - if (destination.Memory.Length != source.Memory.Length) - { - throw new InvalidOperationException("SwapOrCopyContents(): buffers should both owned or the same size!"); - } - - source.Memory.CopyTo(destination.Memory); - } - } - - /// - public void Dispose() - { - this.MemoryOwner?.Dispose(); - } - - private static void SwapContents(ref MemorySource a, ref MemorySource b) - { - IMemoryOwner tempOwner = a.MemoryOwner; - Memory tempMemory = a.Memory; - - a.MemoryOwner = b.MemoryOwner; - a.Memory = b.Memory; - - b.MemoryOwner = tempOwner; - b.Memory = tempMemory; - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs index 146b07d05..877571425 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs @@ -292,7 +292,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg // no need to dispose when buffer is not array owner var memory = new Memory(values); - var source = new MemorySource(memory); + var source = MemoryGroup.Wrap(memory); buffers[i] = new Buffer2D(source, values.Length, 1); } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs index 8d7dda2fe..75e6da5d0 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs @@ -6,6 +6,7 @@ using System.IO; using System.Linq; using SixLabors.ImageSharp.Formats.Jpeg; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; @@ -113,7 +114,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg this.Output.WriteLine($"Component{i}: {diff}"); averageDifference += diff.average; totalDifference += diff.total; - tolerance += libJpegComponent.SpectralBlocks.MemorySource.GetSpan().Length; + tolerance += libJpegComponent.SpectralBlocks.GetSpan().Length; } averageDifference /= componentCount; diff --git a/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs b/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs index 02b59825b..a11602280 100644 --- a/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs +++ b/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs @@ -3,6 +3,7 @@ using System; using System.Buffers; +using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -18,28 +19,34 @@ namespace SixLabors.ImageSharp.Tests.Memory // ReSharper disable once ClassNeverInstantiated.Local private class Assert : Xunit.Assert { - public static void SpanPointsTo(Span span, IMemoryOwner buffer, int bufferOffset = 0) + public static void SpanPointsTo(Span span, Memory buffer, int bufferOffset = 0) where T : struct { ref T actual = ref MemoryMarshal.GetReference(span); - ref T expected = ref Unsafe.Add(ref buffer.GetReference(), bufferOffset); + ref T expected = ref buffer.Span[bufferOffset]; True(Unsafe.AreSame(ref expected, ref actual), "span does not point to the expected position"); } } - private MemoryAllocator MemoryAllocator { get; } = new TestMemoryAllocator(); + private TestMemoryAllocator MemoryAllocator { get; } = new TestMemoryAllocator(); + + private const int Big = 99999; [Theory] - [InlineData(7, 42)] - [InlineData(1025, 17)] - public void Construct(int width, int height) + [InlineData(Big, 7, 42)] + [InlineData(Big, 1025, 17)] + [InlineData(300, 42, 777)] + public unsafe void Construct(int bufferCapacity, int width, int height) { + this.MemoryAllocator.BufferCapacityInBytes = sizeof(TestStructs.Foo) * bufferCapacity; + using (Buffer2D buffer = this.MemoryAllocator.Allocate2D(width, height)) { Assert.Equal(width, buffer.Width); Assert.Equal(height, buffer.Height); - Assert.Equal(width * height, buffer.GetMemory().Length); + Assert.Equal(width * height, buffer.MemoryGroup.TotalLength); + Assert.True(buffer.MemoryGroup.BufferLength % width == 0); } } @@ -57,34 +64,48 @@ namespace SixLabors.ImageSharp.Tests.Memory } [Theory] - [InlineData(7, 42, 0)] - [InlineData(7, 42, 10)] - [InlineData(17, 42, 41)] - public void GetRowSpanY(int width, int height, int y) + [InlineData(Big, 7, 42, 0, 0)] + [InlineData(Big, 7, 42, 10, 0)] + [InlineData(Big, 17, 42, 41, 0)] + [InlineData(500, 17, 42, 41, 1)] + [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) { + this.MemoryAllocator.BufferCapacityInBytes = sizeof(TestStructs.Foo) * bufferCapacity; + using (Buffer2D buffer = this.MemoryAllocator.Allocate2D(width, height)) { Span span = buffer.GetRowSpan(y); - // Assert.Equal(width * y, span.Start); Assert.Equal(width, span.Length); - Assert.SpanPointsTo(span, buffer.MemorySource.MemoryOwner, width * y); + + int expectedSubBufferOffset = (width * y) - (expectedBufferIndex * buffer.MemoryGroup.BufferLength); + Assert.SpanPointsTo(span, buffer.MemoryGroup[expectedBufferIndex], expectedSubBufferOffset); } } [Theory] - [InlineData(42, 8, 0, 0)] - [InlineData(400, 1000, 20, 10)] - [InlineData(99, 88, 98, 87)] - public void Indexer(int width, int height, int x, int y) + [InlineData(Big, 42, 8, 0, 0)] + [InlineData(Big, 400, 1000, 20, 10)] + [InlineData(Big, 99, 88, 98, 87)] + [InlineData(500, 200, 30, 42, 13)] + [InlineData(500, 200, 30, 199, 29)] + public unsafe void Indexer(int bufferCapacity, int width, int height, int x, int y) { + this.MemoryAllocator.BufferCapacityInBytes = sizeof(TestStructs.Foo) * bufferCapacity; + using (Buffer2D buffer = this.MemoryAllocator.Allocate2D(width, height)) { - Span span = buffer.MemorySource.GetSpan(); + int bufferIndex = (width * y) / buffer.MemoryGroup.BufferLength; + int subBufferStart = (width * y) - (bufferIndex * buffer.MemoryGroup.BufferLength); + + Span span = buffer.MemoryGroup[bufferIndex].Span.Slice(subBufferStart); ref TestStructs.Foo actual = ref buffer[x, y]; - ref TestStructs.Foo expected = ref span[(y * width) + x]; + ref TestStructs.Foo expected = ref span[x]; Assert.True(Unsafe.AreSame(ref expected, ref actual)); } @@ -96,13 +117,13 @@ namespace SixLabors.ImageSharp.Tests.Memory using (Buffer2D a = this.MemoryAllocator.Allocate2D(10, 5)) using (Buffer2D b = this.MemoryAllocator.Allocate2D(3, 7)) { - IMemoryOwner aa = a.MemorySource.MemoryOwner; - IMemoryOwner bb = b.MemorySource.MemoryOwner; + Memory aa = a.MemoryGroup.Single(); + Memory bb = b.MemoryGroup.Single(); Buffer2D.SwapOrCopyContent(a, b); - Assert.Equal(bb, a.MemorySource.MemoryOwner); - Assert.Equal(aa, b.MemorySource.MemoryOwner); + Assert.Equal(bb, a.MemoryGroup.Single()); + Assert.Equal(aa, b.MemoryGroup.Single()); Assert.Equal(new Size(3, 7), a.Size()); Assert.Equal(new Size(10, 5), b.Size()); diff --git a/tests/ImageSharp.Tests/Memory/MemorySourceTests.cs b/tests/ImageSharp.Tests/Memory/MemorySourceTests.cs deleted file mode 100644 index d0f8c6f91..000000000 --- a/tests/ImageSharp.Tests/Memory/MemorySourceTests.cs +++ /dev/null @@ -1,159 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Buffers; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; -using Xunit; - -// ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.Memory -{ - public class MemorySourceTests - { - public class Construction - { - [Theory] - [InlineData(false)] - [InlineData(true)] - public void InitializeAsOwner(bool isInternalMemorySource) - { - var data = new Rgba32[21]; - var mmg = new TestMemoryManager(data); - - var a = new MemorySource(mmg, isInternalMemorySource); - - Assert.Equal(mmg, a.MemoryOwner); - Assert.Equal(mmg.Memory, a.Memory); - Assert.Equal(isInternalMemorySource, a.HasSwappableContents); - } - - [Fact] - public void InitializeAsObserver_MemoryOwner_IsNull() - { - var data = new Rgba32[21]; - var mmg = new TestMemoryManager(data); - - var a = new MemorySource(mmg.Memory); - - Assert.Null(a.MemoryOwner); - Assert.Equal(mmg.Memory, a.Memory); - Assert.False(a.HasSwappableContents); - } - } - - public class Dispose - { - [Theory] - [InlineData(false)] - [InlineData(true)] - public void WhenOwnershipIsTransferred_ShouldDisposeMemoryOwner(bool isInternalMemorySource) - { - var mmg = new TestMemoryManager(new int[10]); - var bmg = new MemorySource(mmg, isInternalMemorySource); - - bmg.Dispose(); - Assert.True(mmg.IsDisposed); - } - - [Fact] - public void WhenMemoryObserver_ShouldNotDisposeAnything() - { - var mmg = new TestMemoryManager(new int[10]); - var bmg = new MemorySource(mmg.Memory); - - bmg.Dispose(); - Assert.False(mmg.IsDisposed); - } - } - - public class SwapOrCopyContent - { - private MemoryAllocator MemoryAllocator { get; } = new TestMemoryAllocator(); - - private MemorySource AllocateMemorySource(int length, AllocationOptions options = AllocationOptions.None) - where T : struct - { - IMemoryOwner owner = this.MemoryAllocator.Allocate(length, options); - return new MemorySource(owner, true); - } - - [Fact] - public void WhenBothAreMemoryOwners_ShouldSwap() - { - MemorySource a = this.AllocateMemorySource(13); - MemorySource b = this.AllocateMemorySource(17); - - IMemoryOwner aa = a.MemoryOwner; - IMemoryOwner bb = b.MemoryOwner; - - Memory aaa = a.Memory; - Memory bbb = b.Memory; - - MemorySource.SwapOrCopyContent(ref a, ref b); - - Assert.Equal(bb, a.MemoryOwner); - Assert.Equal(aa, b.MemoryOwner); - - Assert.Equal(bbb, a.Memory); - Assert.Equal(aaa, b.Memory); - Assert.NotEqual(a.Memory, b.Memory); - } - - [Theory] - [InlineData(false, false)] - [InlineData(true, true)] - [InlineData(true, false)] - public void WhenDestIsNotMemoryOwner_SameSize_ShouldCopy(bool sourceIsOwner, bool isInternalMemorySource) - { - var data = new Rgba32[21]; - var color = new Rgba32(1, 2, 3, 4); - - var destOwner = new TestMemoryManager(data); - var dest = new MemorySource(destOwner.Memory); - - IMemoryOwner sourceOwner = this.MemoryAllocator.Allocate(21); - - MemorySource source = sourceIsOwner - ? new MemorySource(sourceOwner, isInternalMemorySource) - : new MemorySource(sourceOwner.Memory); - - sourceOwner.Memory.Span[10] = color; - - // Act: - MemorySource.SwapOrCopyContent(ref dest, ref source); - - // Assert: - Assert.Equal(color, dest.Memory.Span[10]); - Assert.NotEqual(sourceOwner, dest.MemoryOwner); - Assert.NotEqual(destOwner, source.MemoryOwner); - } - - [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); - - var destOwner = new TestMemoryManager(data); - var dest = new MemorySource(destOwner.Memory); - - IMemoryOwner sourceOwner = this.MemoryAllocator.Allocate(22); - - MemorySource source = sourceIsOwner - ? new MemorySource(sourceOwner, true) - : new MemorySource(sourceOwner.Memory); - sourceOwner.Memory.Span[10] = color; - - // Act: - Assert.ThrowsAny(() => MemorySource.SwapOrCopyContent(ref dest, ref source)); - - Assert.Equal(color, source.Memory.Span[10]); - Assert.NotEqual(color, dest.Memory.Span[10]); - } - } - } -} From 35796da28cca4a052068345e6331514eca35416b Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Tue, 4 Feb 2020 02:53:04 +0100 Subject: [PATCH 17/62] Improve robustness of discontiguous Buffer2D --- .../Advanced/AdvancedImageExtensions.cs | 4 +- src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs | 6 +- src/ImageSharp/Formats/Tga/TgaEncoderCore.cs | 2 +- .../ImageFrameCollection{TPixel}.cs | 2 +- src/ImageSharp/ImageFrame{TPixel}.cs | 8 +-- src/ImageSharp/Memory/Buffer2DExtensions.cs | 62 ++++++++----------- src/ImageSharp/Memory/Buffer2D{T}.cs | 45 +++++++++++++- src/ImageSharp/Memory/BufferArea{T}.cs | 8 +-- .../DiscontiguousBuffers/MemoryGroup{T}.cs | 10 ++- .../Transforms/Resize/ResizeKernelMap.cs | 2 +- .../Transforms/Resize/ResizeWorker.cs | 4 +- .../Formats/Jpg/SpectralJpegTests.cs | 2 +- .../Helpers/ParallelHelperTests.cs | 2 +- .../Helpers/RowIntervalTests.cs | 2 +- .../ImageSharp.Tests/Memory/Buffer2DTests.cs | 34 ++++++++-- .../Memory/BufferAreaTests.cs | 2 +- .../MemoryGroupTests.Allocate.cs | 12 +++- .../TestUtilities/TestImageExtensions.cs | 2 +- 18 files changed, 140 insertions(+), 69 deletions(-) diff --git a/src/ImageSharp/Advanced/AdvancedImageExtensions.cs b/src/ImageSharp/Advanced/AdvancedImageExtensions.cs index cfaffbbb2..79a863ff4 100644 --- a/src/ImageSharp/Advanced/AdvancedImageExtensions.cs +++ b/src/ImageSharp/Advanced/AdvancedImageExtensions.cs @@ -129,7 +129,7 @@ namespace SixLabors.ImageSharp.Advanced internal static Memory GetPixelMemory(this ImageFrame source) where TPixel : struct, IPixel { - return source.PixelBuffer.GetMemory(); + return source.PixelBuffer.GetSingleMemory(); } /// @@ -185,6 +185,6 @@ namespace SixLabors.ImageSharp.Advanced /// A reference to the element. private static ref TPixel DangerousGetPinnableReferenceToPixelBuffer(IPixelSource source) where TPixel : struct, IPixel - => ref MemoryMarshal.GetReference(source.PixelBuffer.GetSpan()); + => ref MemoryMarshal.GetReference(source.PixelBuffer.GetSingleSpan()); } } diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs index 8d82d28fb..3e1637e70 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs @@ -301,11 +301,11 @@ namespace SixLabors.ImageSharp.Formats.Bmp Span rowsWithUndefinedPixelsSpan = rowsWithUndefinedPixels.Memory.Span; if (compression == BmpCompression.RLE8) { - this.UncompressRle8(width, buffer.GetSpan(), undefinedPixels.GetSpan(), rowsWithUndefinedPixelsSpan); + this.UncompressRle8(width, buffer.GetSingleSpan(), undefinedPixels.GetSingleSpan(), rowsWithUndefinedPixelsSpan); } else { - this.UncompressRle4(width, buffer.GetSpan(), undefinedPixels.GetSpan(), rowsWithUndefinedPixelsSpan); + this.UncompressRle4(width, buffer.GetSingleSpan(), undefinedPixels.GetSingleSpan(), rowsWithUndefinedPixelsSpan); } for (int y = 0; y < height; y++) @@ -377,7 +377,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp { Span rowsWithUndefinedPixelsSpan = rowsWithUndefinedPixels.Memory.Span; Span bufferSpan = buffer.GetSpan(); - this.UncompressRle24(width, bufferSpan, undefinedPixels.GetSpan(), rowsWithUndefinedPixelsSpan); + this.UncompressRle24(width, bufferSpan, undefinedPixels.GetSingleSpan(), rowsWithUndefinedPixelsSpan); for (int y = 0; y < height; y++) { int newY = Invert(y, height, inverted); diff --git a/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs b/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs index a4b141f38..1306061c5 100644 --- a/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs +++ b/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs @@ -155,7 +155,7 @@ namespace SixLabors.ImageSharp.Formats.Tga { Rgba32 color = default; Buffer2D pixels = image.PixelBuffer; - Span pixelSpan = pixels.GetSpan(); + Span pixelSpan = pixels.GetSingleSpan(); int totalPixels = image.Width * image.Height; int encodedPixels = 0; while (encodedPixels < totalPixels) diff --git a/src/ImageSharp/ImageFrameCollection{TPixel}.cs b/src/ImageSharp/ImageFrameCollection{TPixel}.cs index b11c74958..635ed5b77 100644 --- a/src/ImageSharp/ImageFrameCollection{TPixel}.cs +++ b/src/ImageSharp/ImageFrameCollection{TPixel}.cs @@ -351,7 +351,7 @@ namespace SixLabors.ImageSharp this.parent.GetConfiguration(), source.Size(), source.Metadata.DeepClone()); - source.CopyPixelsTo(result.PixelBuffer.GetSpan()); + source.CopyPixelsTo(result.PixelBuffer.GetSingleSpan()); return result; } } diff --git a/src/ImageSharp/ImageFrame{TPixel}.cs b/src/ImageSharp/ImageFrame{TPixel}.cs index 421cd1032..0d69b7666 100644 --- a/src/ImageSharp/ImageFrame{TPixel}.cs +++ b/src/ImageSharp/ImageFrame{TPixel}.cs @@ -133,7 +133,7 @@ namespace SixLabors.ImageSharp Guard.NotNull(source, nameof(source)); this.PixelBuffer = this.GetConfiguration().MemoryAllocator.Allocate2D(source.PixelBuffer.Width, source.PixelBuffer.Height); - source.PixelBuffer.GetSpan().CopyTo(this.PixelBuffer.GetSpan()); + source.PixelBuffer.GetSingleSpan().CopyTo(this.PixelBuffer.GetSingleSpan()); } /// @@ -179,7 +179,7 @@ namespace SixLabors.ImageSharp throw new ArgumentException("ImageFrame.CopyTo(): target must be of the same size!", nameof(target)); } - this.GetPixelSpan().CopyTo(target.GetSpan()); + this.GetPixelSpan().CopyTo(target.GetSingleSpan()); } /// @@ -216,10 +216,10 @@ namespace SixLabors.ImageSharp if (typeof(TPixel) == typeof(TDestinationPixel)) { Span dest1 = MemoryMarshal.Cast(destination); - this.PixelBuffer.GetSpan().CopyTo(dest1); + this.PixelBuffer.GetSingleSpan().CopyTo(dest1); } - PixelOperations.Instance.To(this.GetConfiguration(), this.PixelBuffer.GetSpan(), destination); + PixelOperations.Instance.To(this.GetConfiguration(), this.PixelBuffer.GetSingleSpan(), destination); } /// diff --git a/src/ImageSharp/Memory/Buffer2DExtensions.cs b/src/ImageSharp/Memory/Buffer2DExtensions.cs index 959ad94bb..829a2767a 100644 --- a/src/ImageSharp/Memory/Buffer2DExtensions.cs +++ b/src/ImageSharp/Memory/Buffer2DExtensions.cs @@ -15,59 +15,49 @@ namespace SixLabors.ImageSharp.Memory public static class Buffer2DExtensions { /// - /// Gets a to the backing buffer of . + /// Gets a to the backing data of + /// if the backing group consists of one single contiguous memory buffer. + /// Throws otherwise. /// /// The . /// The value type. /// The referencing the memory area. - public static Span GetSpan(this Buffer2D buffer) + /// + /// Thrown when the backing group is discontiguous. + /// + public static Span GetSingleSpan(this Buffer2D buffer) where T : struct { Guard.NotNull(buffer, nameof(buffer)); + if (buffer.MemoryGroup.Count > 1) + { + throw new InvalidOperationException("GetSingleSpan is only valid for a single-buffer group!"); + } + return buffer.MemoryGroup.Single().Span; } /// - /// Gets the holding the backing buffer of . + /// Gets a to the backing data of + /// if the backing group consists of one single contiguous memory buffer. + /// Throws otherwise. /// /// The . /// The value type. /// The . - public static Memory GetMemory(this Buffer2D buffer) - where T : struct - { - Guard.NotNull(buffer, nameof(buffer)); - return buffer.MemoryGroup.Single(); - } - - /// - /// Gets a to the row 'y' beginning from the pixel at the first pixel on that row. - /// - /// The buffer - /// The y (row) coordinate - /// The element type - /// The - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Span GetRowSpan(this Buffer2D buffer, int y) + /// + /// Thrown when the backing group is discontiguous. + /// + public static Memory GetSingleMemory(this Buffer2D buffer) where T : struct { Guard.NotNull(buffer, nameof(buffer)); - return buffer.MemoryGroup.GetBoundedSlice(y * buffer.Width, buffer.Width).Span; - } + if (buffer.MemoryGroup.Count > 1) + { + throw new InvalidOperationException("GetSingleMemory is only valid for a single-buffer group!"); + } - /// - /// Gets a to the row 'y' beginning from the pixel at the first pixel on that row. - /// - /// The buffer - /// The y (row) coordinate - /// The element type - /// The - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Memory GetRowMemory(this Buffer2D buffer, int y) - where T : struct - { - Guard.NotNull(buffer, nameof(buffer)); - return buffer.MemoryGroup.GetBoundedSlice(y * buffer.Width, buffer.Width); + return buffer.MemoryGroup.Single(); } /// @@ -93,7 +83,7 @@ namespace SixLabors.ImageSharp.Memory int dOffset = destIndex * elementSize; long count = columnCount * elementSize; - Span span = MemoryMarshal.AsBytes(buffer.GetMemory().Span); + Span span = MemoryMarshal.AsBytes(buffer.GetSingleMemory().Span); fixed (byte* ptr = span) { @@ -153,7 +143,7 @@ namespace SixLabors.ImageSharp.Memory internal static Span GetMultiRowSpan(this Buffer2D buffer, in RowInterval rows) where T : struct { - return buffer.GetSpan().Slice(rows.Min * buffer.Width, rows.Height * buffer.Width); + return buffer.GetSingleSpan().Slice(rows.Min * buffer.Width, rows.Height * buffer.Width); } /// diff --git a/src/ImageSharp/Memory/Buffer2D{T}.cs b/src/ImageSharp/Memory/Buffer2D{T}.cs index 439a5bde2..ea2568efd 100644 --- a/src/ImageSharp/Memory/Buffer2D{T}.cs +++ b/src/ImageSharp/Memory/Buffer2D{T}.cs @@ -17,6 +17,8 @@ namespace SixLabors.ImageSharp.Memory public sealed class Buffer2D : IDisposable where T : struct { + private Memory cachedMemory = default; + /// /// Initializes a new instance of the class. /// @@ -28,6 +30,11 @@ namespace SixLabors.ImageSharp.Memory this.MemoryGroup = memoryGroup; this.Width = width; this.Height = height; + + if (memoryGroup.Count == 1) + { + this.cachedMemory = memoryGroup[0]; + } } /// @@ -63,12 +70,39 @@ namespace SixLabors.ImageSharp.Memory } } + /// + /// Gets a to the row 'y' beginning from the pixel at the first pixel on that row. + /// + /// The y (row) coordinate. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Span GetRowSpan(int y) + { + return this.cachedMemory.Length > 0 + ? this.cachedMemory.Span.Slice(y * this.Width, this.Width) + : this.GetRowMemorySlow(y).Span; + } + + /// + /// Gets a to the row 'y' beginning from the pixel at the first pixel on that row. + /// + /// The y (row) coordinate. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Memory GetRowMemory(int y) + { + return this.cachedMemory.Length > 0 + ? this.cachedMemory.Slice(y * this.Width, this.Width) + : this.GetRowMemorySlow(y); + } + /// /// Disposes the instance /// public void Dispose() { this.MemoryGroup.Dispose(); + this.cachedMemory = default; } /// @@ -78,10 +112,13 @@ namespace SixLabors.ImageSharp.Memory internal static void SwapOrCopyContent(Buffer2D destination, Buffer2D source) { MemoryGroup.SwapOrCopyContent(destination.MemoryGroup, source.MemoryGroup); - SwapDimensionData(destination, source); + SwapOwnData(destination, source); } - private static void SwapDimensionData(Buffer2D a, Buffer2D b) + [MethodImpl(InliningOptions.ColdPath)] + private Memory GetRowMemorySlow(int y) => this.MemoryGroup.GetBoundedSlice(y * this.Width, this.Width); + + private static void SwapOwnData(Buffer2D a, Buffer2D b) { Size aSize = a.Size(); Size bSize = b.Size(); @@ -91,6 +128,10 @@ namespace SixLabors.ImageSharp.Memory a.Width = bSize.Width; a.Height = bSize.Height; + + Memory aCached = a.cachedMemory; + a.cachedMemory = b.cachedMemory; + b.cachedMemory = aCached; } } } diff --git a/src/ImageSharp/Memory/BufferArea{T}.cs b/src/ImageSharp/Memory/BufferArea{T}.cs index ec7665998..983d6b3a7 100644 --- a/src/ImageSharp/Memory/BufferArea{T}.cs +++ b/src/ImageSharp/Memory/BufferArea{T}.cs @@ -72,7 +72,7 @@ namespace SixLabors.ImageSharp.Memory /// The position inside a row /// The row index /// The reference to the value - public ref T this[int x, int y] => ref this.DestinationBuffer.GetSpan()[this.GetIndexOf(x, y)]; + public ref T this[int x, int y] => ref this.DestinationBuffer.GetSingleSpan()[this.GetIndexOf(x, y)]; /// /// Gets a reference to the [0,0] element. @@ -80,7 +80,7 @@ namespace SixLabors.ImageSharp.Memory /// The reference to the [0,0] element [MethodImpl(MethodImplOptions.AggressiveInlining)] public ref T GetReferenceToOrigin() => - ref this.DestinationBuffer.GetSpan()[(this.Rectangle.Y * this.DestinationBuffer.Width) + this.Rectangle.X]; + ref this.DestinationBuffer.GetSingleSpan()[(this.Rectangle.Y * this.DestinationBuffer.Width) + this.Rectangle.X]; /// /// Gets a span to row 'y' inside this area. @@ -94,7 +94,7 @@ namespace SixLabors.ImageSharp.Memory int xx = this.Rectangle.X; int width = this.Rectangle.Width; - return this.DestinationBuffer.GetSpan().Slice(yy + xx, width); + return this.DestinationBuffer.GetSingleSpan().Slice(yy + xx, width); } /// @@ -148,7 +148,7 @@ namespace SixLabors.ImageSharp.Memory // Optimization for when the size of the area is the same as the buffer size. if (this.IsFullBufferArea) { - this.DestinationBuffer.GetSpan().Clear(); + this.DestinationBuffer.GetSingleSpan().Clear(); return; } diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs index d9c384b55..00bd27b34 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 { @@ -69,15 +70,22 @@ namespace SixLabors.ImageSharp.Memory { Guard.NotNull(allocator, nameof(allocator)); Guard.MustBeGreaterThanOrEqualTo(totalLength, 0, nameof(totalLength)); - Guard.MustBeGreaterThan(bufferAlignment, 0, nameof(bufferAlignment)); + Guard.MustBeGreaterThanOrEqualTo(bufferAlignment, 0, nameof(bufferAlignment)); int blockCapacityInElements = allocator.GetBufferCapacityInBytes() / ElementSize; + if (bufferAlignment > blockCapacityInElements) { throw new InvalidMemoryOperationException( $"The buffer capacity of the provided MemoryAllocator is insufficient for the requested buffer alignment: {bufferAlignment}."); } + if (totalLength == 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) diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs index 1b653a92c..06eef76e2 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs @@ -55,7 +55,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms this.DestinationLength = destinationLength; this.MaxDiameter = (radius * 2) + 1; this.data = memoryAllocator.Allocate2D(this.MaxDiameter, bufferHeight, AllocationOptions.Clean); - this.pinHandle = this.data.GetMemory().Pin(); + this.pinHandle = this.data.GetSingleMemory().Pin(); this.kernels = new ResizeKernel[destinationLength]; this.tempValues = new double[this.MaxDiameter]; } diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs index 4f5faa38e..57663c07d 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs @@ -113,7 +113,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms public void FillDestinationPixels(RowInterval rowInterval, Buffer2D destination) { Span tempColSpan = this.tempColumnBuffer.GetSpan(); - Span transposedFirstPassBufferSpan = this.transposedFirstPassBuffer.GetSpan(); + Span transposedFirstPassBufferSpan = this.transposedFirstPassBuffer.GetSingleSpan(); for (int y = rowInterval.Min; y < rowInterval.Max; y++) { @@ -165,7 +165,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms private void CalculateFirstPassValues(RowInterval calculationInterval) { Span tempRowSpan = this.tempRowBuffer.GetSpan(); - Span transposedFirstPassBufferSpan = this.transposedFirstPassBuffer.GetSpan(); + Span transposedFirstPassBufferSpan = this.transposedFirstPassBuffer.GetSingleSpan(); for (int y = calculationInterval.Min; y < calculationInterval.Max; y++) { diff --git a/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs index 75e6da5d0..c69740ede 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs @@ -114,7 +114,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg this.Output.WriteLine($"Component{i}: {diff}"); averageDifference += diff.average; totalDifference += diff.total; - tolerance += libJpegComponent.SpectralBlocks.GetSpan().Length; + tolerance += libJpegComponent.SpectralBlocks.GetSingleSpan().Length; } averageDifference /= componentCount; diff --git a/tests/ImageSharp.Tests/Helpers/ParallelHelperTests.cs b/tests/ImageSharp.Tests/Helpers/ParallelHelperTests.cs index 5914aba40..0d3002661 100644 --- a/tests/ImageSharp.Tests/Helpers/ParallelHelperTests.cs +++ b/tests/ImageSharp.Tests/Helpers/ParallelHelperTests.cs @@ -330,7 +330,7 @@ namespace SixLabors.ImageSharp.Tests.Helpers }); // Assert: - TestImageExtensions.CompareBuffers(expected.GetSpan(), actual.GetSpan()); + TestImageExtensions.CompareBuffers(expected.GetSingleSpan(), actual.GetSingleSpan()); } } diff --git a/tests/ImageSharp.Tests/Helpers/RowIntervalTests.cs b/tests/ImageSharp.Tests/Helpers/RowIntervalTests.cs index 0bb3f49d6..222770195 100644 --- a/tests/ImageSharp.Tests/Helpers/RowIntervalTests.cs +++ b/tests/ImageSharp.Tests/Helpers/RowIntervalTests.cs @@ -25,7 +25,7 @@ namespace SixLabors.ImageSharp.Tests.Helpers Span span = buffer.GetMultiRowSpan(rows); - ref int expected0 = ref buffer.GetSpan()[min * width]; + ref int expected0 = ref buffer.GetSingleSpan()[min * width]; int expectedLength = (max - min) * width; ref int actual0 = ref span[0]; diff --git a/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs b/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs index a11602280..03a5f2273 100644 --- a/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs +++ b/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs @@ -50,12 +50,30 @@ namespace SixLabors.ImageSharp.Tests.Memory } } + [Theory] + [InlineData(Big, 0, 42)] + [InlineData(Big, 1, 0)] + [InlineData(60, 42, 0)] + [InlineData(3, 0, 0)] + public unsafe void Construct_Empty(int bufferCapacity, int width, int height) + { + this.MemoryAllocator.BufferCapacityInBytes = sizeof(TestStructs.Foo) * bufferCapacity; + + using (Buffer2D buffer = this.MemoryAllocator.Allocate2D(width, height)) + { + Assert.Equal(width, buffer.Width); + Assert.Equal(height, buffer.Height); + Assert.Equal(0, buffer.MemoryGroup.TotalLength); + Assert.Equal(0, buffer.GetSingleSpan().Length); + } + } + [Fact] public void CreateClean() { using (Buffer2D buffer = this.MemoryAllocator.Allocate2D(42, 42, AllocationOptions.Clean)) { - Span span = buffer.GetSpan(); + Span span = buffer.GetSingleSpan(); for (int j = 0; j < span.Length; j++) { Assert.Equal(0, span[j]); @@ -114,9 +132,12 @@ namespace SixLabors.ImageSharp.Tests.Memory [Fact] public void SwapOrCopyContent() { - using (Buffer2D a = this.MemoryAllocator.Allocate2D(10, 5)) - using (Buffer2D b = this.MemoryAllocator.Allocate2D(3, 7)) + 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.MemoryGroup.Single(); Memory bb = b.MemoryGroup.Single(); @@ -127,6 +148,9 @@ namespace SixLabors.ImageSharp.Tests.Memory 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]); } } @@ -142,7 +166,7 @@ namespace SixLabors.ImageSharp.Tests.Memory var rnd = new Random(123); using (Buffer2D b = this.MemoryAllocator.Allocate2D(width, height)) { - rnd.RandomFill(b.GetSpan(), 0, 1); + rnd.RandomFill(b.GetSingleSpan(), 0, 1); b.CopyColumns(startIndex, destIndex, columnCount); @@ -164,7 +188,7 @@ namespace SixLabors.ImageSharp.Tests.Memory var rnd = new Random(123); using (Buffer2D b = this.MemoryAllocator.Allocate2D(100, 100)) { - rnd.RandomFill(b.GetSpan(), 0, 1); + rnd.RandomFill(b.GetSingleSpan(), 0, 1); b.CopyColumns(0, 50, 22); b.CopyColumns(0, 50, 22); diff --git a/tests/ImageSharp.Tests/Memory/BufferAreaTests.cs b/tests/ImageSharp.Tests/Memory/BufferAreaTests.cs index 9f523156f..a0112ce90 100644 --- a/tests/ImageSharp.Tests/Memory/BufferAreaTests.cs +++ b/tests/ImageSharp.Tests/Memory/BufferAreaTests.cs @@ -117,7 +117,7 @@ namespace SixLabors.ImageSharp.Tests.Memory using (Buffer2D buffer = CreateTestBuffer(22, 13)) { buffer.GetArea().Clear(); - Span fullSpan = buffer.GetSpan(); + Span fullSpan = buffer.GetSingleSpan(); Assert.True(fullSpan.SequenceEqual(new int[fullSpan.Length])); } } diff --git a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.Allocate.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.Allocate.cs index 972f6cb26..298b5a93f 100644 --- a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.Allocate.cs +++ b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.Allocate.cs @@ -21,7 +21,10 @@ namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers { default(S5), 22, 4, 7, 2, 4, 3 }, { default(S5), 22, 4, 8, 2, 4, 4 }, { default(S5), 22, 4, 21, 6, 4, 1 }, - { default(S5), 22, 4, 0, 0, 4, -1 }, + + // empty: + { default(S5), 22, 0, 0, 1, -1, 0 }, + { default(S5), 22, 4, 0, 1, -1, 0 }, { default(S4), 50, 12, 12, 1, 12, 12 }, { default(S4), 50, 7, 12, 2, 7, 5 }, @@ -61,7 +64,12 @@ namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers // Assert: Assert.Equal(expectedNumberOfBuffers, g.Count); - Assert.Equal(expectedBufferSize, g.BufferLength); + + if (expectedBufferSize >= 0) + { + Assert.Equal(expectedBufferSize, g.BufferLength); + } + if (g.Count == 0) { return; diff --git a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs index 70d39024e..8cf53bf85 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs @@ -672,7 +672,7 @@ namespace SixLabors.ImageSharp.Tests Span pixels = image.Frames.RootFrame.GetPixelSpan(); - Span bufferSpan = buffer.GetSpan(); + Span bufferSpan = buffer.GetSingleSpan(); for (int i = 0; i < bufferSpan.Length; i++) { From a3a0a3d1d8e7541579332b3abf42cefdf287647a Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Tue, 4 Feb 2020 03:24:15 +0100 Subject: [PATCH 18/62] robust BufferArea --- src/ImageSharp/Memory/BufferArea{T}.cs | 24 +-- .../MemoryGroupExtensions.cs | 18 ++ .../Memory/BufferAreaTests.cs | 162 ++++++++++-------- .../DiscontiguousBuffers/MemoryGroupTests.cs | 27 +++ 4 files changed, 140 insertions(+), 91 deletions(-) diff --git a/src/ImageSharp/Memory/BufferArea{T}.cs b/src/ImageSharp/Memory/BufferArea{T}.cs index 983d6b3a7..b9210e301 100644 --- a/src/ImageSharp/Memory/BufferArea{T}.cs +++ b/src/ImageSharp/Memory/BufferArea{T}.cs @@ -72,7 +72,7 @@ namespace SixLabors.ImageSharp.Memory /// The position inside a row /// The row index /// The reference to the value - public ref T this[int x, int y] => ref this.DestinationBuffer.GetSingleSpan()[this.GetIndexOf(x, y)]; + public ref T this[int x, int y] => ref this.DestinationBuffer[x + this.Rectangle.X, y + this.Rectangle.Y]; /// /// Gets a reference to the [0,0] element. @@ -80,7 +80,7 @@ namespace SixLabors.ImageSharp.Memory /// The reference to the [0,0] element [MethodImpl(MethodImplOptions.AggressiveInlining)] public ref T GetReferenceToOrigin() => - ref this.DestinationBuffer.GetSingleSpan()[(this.Rectangle.Y * this.DestinationBuffer.Width) + this.Rectangle.X]; + ref this.GetRowSpan(0)[0]; /// /// Gets a span to row 'y' inside this area. @@ -94,16 +94,16 @@ namespace SixLabors.ImageSharp.Memory int xx = this.Rectangle.X; int width = this.Rectangle.Width; - return this.DestinationBuffer.GetSingleSpan().Slice(yy + xx, width); + return this.DestinationBuffer.MemoryGroup.GetBoundedSlice(yy + xx, width).Span; } /// /// Returns a sub-area as . (Similar to .) /// - /// The x index at the subarea origo - /// The y index at the subarea origo - /// The desired width of the subarea - /// The desired height of the subarea + /// The x index at the subarea origin. + /// The y index at the subarea origin. + /// The desired width of the subarea. + /// The desired height of the subarea. /// The subarea [MethodImpl(MethodImplOptions.AggressiveInlining)] public BufferArea GetSubArea(int x, int y, int width, int height) @@ -129,14 +129,6 @@ namespace SixLabors.ImageSharp.Memory return new BufferArea(this.DestinationBuffer, rectangle); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int GetIndexOf(int x, int y) - { - int yy = this.GetRowIndex(y); - int xx = this.Rectangle.X + x; - return yy + xx; - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] internal int GetRowIndex(int y) { @@ -148,7 +140,7 @@ namespace SixLabors.ImageSharp.Memory // Optimization for when the size of the area is the same as the buffer size. if (this.IsFullBufferArea) { - this.DestinationBuffer.GetSingleSpan().Clear(); + this.DestinationBuffer.MemoryGroup.Clear(); return; } diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupExtensions.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupExtensions.cs index ce719dc91..1b4c297f3 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupExtensions.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupExtensions.cs @@ -7,6 +7,24 @@ namespace SixLabors.ImageSharp.Memory { internal static class MemoryGroupExtensions { + public static void Fill(this IMemoryGroup group, T value) + where T : struct + { + foreach (Memory memory in group) + { + memory.Span.Fill(value); + } + } + + public static void Clear(this IMemoryGroup group) + where T : struct + { + foreach (Memory memory in group) + { + memory.Span.Clear(); + } + } + /// /// Returns a slice that is expected to be within the bounds of a single buffer. /// Otherwise is thrown. diff --git a/tests/ImageSharp.Tests/Memory/BufferAreaTests.cs b/tests/ImageSharp.Tests/Memory/BufferAreaTests.cs index a0112ce90..77e899a4c 100644 --- a/tests/ImageSharp.Tests/Memory/BufferAreaTests.cs +++ b/tests/ImageSharp.Tests/Memory/BufferAreaTests.cs @@ -9,22 +9,22 @@ namespace SixLabors.ImageSharp.Tests.Memory { public class BufferAreaTests { + private readonly TestMemoryAllocator memoryAllocator = new TestMemoryAllocator(); + [Fact] public void Construct() { - using (var buffer = Configuration.Default.MemoryAllocator.Allocate2D(10, 20)) - { - var rectangle = new Rectangle(3, 2, 5, 6); - var area = new BufferArea(buffer, rectangle); + using Buffer2D buffer = this.memoryAllocator.Allocate2D(10, 20); + var rectangle = new Rectangle(3, 2, 5, 6); + var area = new BufferArea(buffer, rectangle); - Assert.Equal(buffer, area.DestinationBuffer); - Assert.Equal(rectangle, area.Rectangle); - } + Assert.Equal(buffer, area.DestinationBuffer); + Assert.Equal(rectangle, area.Rectangle); } - private static Buffer2D CreateTestBuffer(int w, int h) + private Buffer2D CreateTestBuffer(int w, int h) { - var buffer = Configuration.Default.MemoryAllocator.Allocate2D(w, h); + Buffer2D buffer = this.memoryAllocator.Allocate2D(w, h); for (int y = 0; y < h; y++) { for (int x = 0; x < w; x++) @@ -37,110 +37,122 @@ namespace SixLabors.ImageSharp.Tests.Memory } [Theory] - [InlineData(2, 3, 2, 2)] - [InlineData(5, 4, 3, 2)] - public void Indexer(int rx, int ry, int x, int y) + [InlineData(1000, 2, 3, 2, 2)] + [InlineData(1000, 5, 4, 3, 2)] + [InlineData(200, 2, 3, 2, 2)] + [InlineData(200, 5, 4, 3, 2)] + public void Indexer(int bufferCapacity, int rx, int ry, int x, int y) { - using (Buffer2D buffer = CreateTestBuffer(20, 30)) - { - var r = new Rectangle(rx, ry, 5, 6); + this.memoryAllocator.BufferCapacityInBytes = sizeof(int) * bufferCapacity; + using Buffer2D buffer = this.CreateTestBuffer(20, 30); + var r = new Rectangle(rx, ry, 5, 6); - BufferArea area = buffer.GetArea(r); + BufferArea area = buffer.GetArea(r); - int value = area[x, y]; - int expected = ((ry + y) * 100) + rx + x; - Assert.Equal(expected, value); - } + int value = area[x, y]; + int expected = ((ry + y) * 100) + rx + x; + Assert.Equal(expected, value); } [Theory] - [InlineData(2, 3, 2, 5, 6)] - [InlineData(5, 4, 3, 6, 5)] - public void GetRowSpan(int rx, int ry, int y, int w, int h) + [InlineData(1000, 2, 3, 2, 5, 6)] + [InlineData(1000, 5, 4, 3, 6, 5)] + [InlineData(200, 2, 3, 2, 5, 6)] + [InlineData(200, 5, 4, 3, 6, 5)] + public void GetRowSpan(int bufferCapacity, int rx, int ry, int y, int w, int h) { - using (Buffer2D buffer = CreateTestBuffer(20, 30)) - { - var r = new Rectangle(rx, ry, w, h); + this.memoryAllocator.BufferCapacityInBytes = sizeof(int) * bufferCapacity; - BufferArea area = buffer.GetArea(r); + using Buffer2D buffer = this.CreateTestBuffer(20, 30); + var r = new Rectangle(rx, ry, w, h); - Span span = area.GetRowSpan(y); + BufferArea area = buffer.GetArea(r); - Assert.Equal(w, span.Length); + Span span = area.GetRowSpan(y); - for (int i = 0; i < w; i++) - { - int expected = ((ry + y) * 100) + rx + i; - int value = span[i]; + Assert.Equal(w, span.Length); - Assert.Equal(expected, value); - } + for (int i = 0; i < w; i++) + { + int expected = ((ry + y) * 100) + rx + i; + int value = span[i]; + + Assert.Equal(expected, value); } } [Fact] public void GetSubArea() { - using (Buffer2D buffer = CreateTestBuffer(20, 30)) - { - BufferArea area0 = buffer.GetArea(6, 8, 10, 10); + using Buffer2D buffer = this.CreateTestBuffer(20, 30); + BufferArea area0 = buffer.GetArea(6, 8, 10, 10); - BufferArea area1 = area0.GetSubArea(4, 4, 5, 5); + BufferArea area1 = area0.GetSubArea(4, 4, 5, 5); - var expectedRect = new Rectangle(10, 12, 5, 5); + var expectedRect = new Rectangle(10, 12, 5, 5); - Assert.Equal(buffer, area1.DestinationBuffer); - Assert.Equal(expectedRect, area1.Rectangle); + Assert.Equal(buffer, area1.DestinationBuffer); + Assert.Equal(expectedRect, area1.Rectangle); - int value00 = (12 * 100) + 10; - Assert.Equal(value00, area1[0, 0]); - } + int value00 = (12 * 100) + 10; + Assert.Equal(value00, area1[0, 0]); } - [Fact] - public void DangerousGetPinnableReference() + [Theory] + [InlineData(1000)] + [InlineData(40)] + public void GetReferenceToOrigin(int bufferCapacity) { - using (Buffer2D buffer = CreateTestBuffer(20, 30)) - { - BufferArea area0 = buffer.GetArea(6, 8, 10, 10); + this.memoryAllocator.BufferCapacityInBytes = sizeof(int) * bufferCapacity; - ref int r = ref area0.GetReferenceToOrigin(); + using Buffer2D buffer = this.CreateTestBuffer(20, 30); + BufferArea area0 = buffer.GetArea(6, 8, 10, 10); - int expected = buffer[6, 8]; - Assert.Equal(expected, r); - } + ref int r = ref area0.GetReferenceToOrigin(); + + int expected = buffer[6, 8]; + Assert.Equal(expected, r); } - [Fact] - public void Clear_FullArea() + [Theory] + [InlineData(1000)] + [InlineData(70)] + public void Clear_FullArea(int bufferCapacity) { - using (Buffer2D buffer = CreateTestBuffer(22, 13)) + this.memoryAllocator.BufferCapacityInBytes = sizeof(int) * bufferCapacity; + + using Buffer2D buffer = this.CreateTestBuffer(22, 13); + var emptyRow = new int[22]; + buffer.GetArea().Clear(); + + for (int y = 0; y < 13; y++) { - buffer.GetArea().Clear(); - Span fullSpan = buffer.GetSingleSpan(); - Assert.True(fullSpan.SequenceEqual(new int[fullSpan.Length])); + Span row = buffer.GetRowSpan(y); + Assert.True(row.SequenceEqual(emptyRow)); } } - [Fact] - public void Clear_SubArea() + [Theory] + [InlineData(1000)] + [InlineData(40)] + public void Clear_SubArea(int bufferCapacity) { - using (Buffer2D buffer = CreateTestBuffer(20, 30)) - { - BufferArea area = buffer.GetArea(5, 5, 10, 10); - area.Clear(); + this.memoryAllocator.BufferCapacityInBytes = sizeof(int) * bufferCapacity; - Assert.NotEqual(0, buffer[4, 4]); - Assert.NotEqual(0, buffer[15, 15]); + using Buffer2D buffer = this.CreateTestBuffer(20, 30); + BufferArea area = buffer.GetArea(5, 5, 10, 10); + area.Clear(); - Assert.Equal(0, buffer[5, 5]); - Assert.Equal(0, buffer[14, 14]); + Assert.NotEqual(0, buffer[4, 4]); + Assert.NotEqual(0, buffer[15, 15]); - for (int y = area.Rectangle.Y; y < area.Rectangle.Bottom; y++) - { - Span span = buffer.GetRowSpan(y).Slice(area.Rectangle.X, area.Width); - Assert.True(span.SequenceEqual(new int[area.Width])); - } + Assert.Equal(0, buffer[5, 5]); + Assert.Equal(0, buffer[14, 14]); + + for (int y = area.Rectangle.Y; y < area.Rectangle.Bottom; y++) + { + Span span = buffer.GetRowSpan(y).Slice(area.Rectangle.X, area.Width); + Assert.True(span.SequenceEqual(new int[area.Width])); } } } diff --git a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs index bf081cb55..694c4d32f 100644 --- a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs +++ b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs @@ -3,6 +3,7 @@ using System; using System.Buffers; +using System.Linq; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Memory; using Xunit; @@ -163,6 +164,32 @@ namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers Assert.ThrowsAny(() => group.GetBoundedSlice(start, length)); } + [Fact] + public void Fill() + { + using MemoryGroup group = this.CreateTestGroup(100, 10, true); + group.Fill(42); + + int[] expectedRow = Enumerable.Repeat(42, 10).ToArray(); + foreach (Memory memory in group) + { + Assert.True(memory.Span.SequenceEqual(expectedRow)); + } + } + + [Fact] + public void Clear() + { + using MemoryGroup group = this.CreateTestGroup(100, 10, true); + group.Clear(); + + var expectedRow = new int[10]; + foreach (Memory memory in group) + { + Assert.True(memory.Span.SequenceEqual(expectedRow)); + } + } + private static void MultiplyAllBy2(ReadOnlySpan source, Span target) { Assert.Equal(source.Length, target.Length); From 492e233d975d4be1d90085448ac7cf31daec3bc3 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Tue, 4 Feb 2020 03:47:54 +0100 Subject: [PATCH 19/62] Actually resize a 30k x 30k image --- src/ImageSharp/ImageFrame{TPixel}.cs | 6 +++--- .../Image/LargeImageIntegrationTests.cs | 21 +++++++++++++++++++ 2 files changed, 24 insertions(+), 3 deletions(-) create mode 100644 tests/ImageSharp.Tests/Image/LargeImageIntegrationTests.cs diff --git a/src/ImageSharp/ImageFrame{TPixel}.cs b/src/ImageSharp/ImageFrame{TPixel}.cs index 0d69b7666..abeb00f74 100644 --- a/src/ImageSharp/ImageFrame{TPixel}.cs +++ b/src/ImageSharp/ImageFrame{TPixel}.cs @@ -284,15 +284,15 @@ namespace SixLabors.ImageSharp /// The value to initialize the bitmap with. internal void Clear(TPixel value) { - Span span = this.GetPixelSpan(); + MemoryGroup group = this.PixelBuffer.MemoryGroup; if (value.Equals(default)) { - span.Clear(); + group.Clear(); } else { - span.Fill(value); + group.Fill(value); } } } diff --git a/tests/ImageSharp.Tests/Image/LargeImageIntegrationTests.cs b/tests/ImageSharp.Tests/Image/LargeImageIntegrationTests.cs new file mode 100644 index 000000000..c8a8baf1d --- /dev/null +++ b/tests/ImageSharp.Tests/Image/LargeImageIntegrationTests.cs @@ -0,0 +1,21 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using Xunit; + +namespace SixLabors.ImageSharp.Tests +{ + public class LargeImageIntegrationTests + { + [Theory(Skip = "For local testing only.")] + [WithBasicTestPatternImages(width: 30000, height: 30000, PixelTypes.Rgba32)] + public void CreateAndResize(TestImageProvider provider) + { + using Image image = provider.GetImage(); + image.Mutate(c => c.Resize(1000, 1000)); + image.DebugSave(provider); + } + } +} From d2b99294f75d50fc2c6dcb3c0eb2a2f6e4e4d0f7 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Wed, 5 Feb 2020 00:50:35 +0100 Subject: [PATCH 20/62] Decode Jpegs to non-contiguous buffers --- src/ImageSharp/ImageFrame{TPixel}.cs | 2 +- .../Formats/Jpg/JpegDecoderTests.Baseline.cs | 21 ++++++++++++++----- .../Jpg/JpegDecoderTests.Progressive.cs | 21 +++++++++++++------ .../Formats/Jpg/JpegDecoderTests.cs | 21 +++++++------------ .../ImageProviders/FileProvider.cs | 20 +++++++++--------- .../TestUtilities/TestImageExtensions.cs | 11 +++++++++- 6 files changed, 60 insertions(+), 36 deletions(-) diff --git a/src/ImageSharp/ImageFrame{TPixel}.cs b/src/ImageSharp/ImageFrame{TPixel}.cs index abeb00f74..bc45a45fd 100644 --- a/src/ImageSharp/ImageFrame{TPixel}.cs +++ b/src/ImageSharp/ImageFrame{TPixel}.cs @@ -133,7 +133,7 @@ namespace SixLabors.ImageSharp Guard.NotNull(source, nameof(source)); this.PixelBuffer = this.GetConfiguration().MemoryAllocator.Allocate2D(source.PixelBuffer.Width, source.PixelBuffer.Height); - source.PixelBuffer.GetSingleSpan().CopyTo(this.PixelBuffer.GetSingleSpan()); + source.PixelBuffer.MemoryGroup.CopyTo(this.PixelBuffer.MemoryGroup); } /// diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Baseline.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Baseline.cs index 628f59a9a..69c0aa87a 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Baseline.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Baseline.cs @@ -4,6 +4,7 @@ using Microsoft.DotNet.RemoteExecutor; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.TestUtilities; +using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using Xunit; // ReSharper disable InconsistentNaming @@ -12,17 +13,23 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg public partial class JpegDecoderTests { [Theory] - [WithFileCollection(nameof(BaselineTestJpegs), PixelTypes.Rgba32)] - public void DecodeBaselineJpeg(TestImageProvider provider) + [WithFileCollection(nameof(BaselineTestJpegs), PixelTypes.Rgba32, false)] + [WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgba32, true)] + public void DecodeBaselineJpeg(TestImageProvider provider, bool enforceNonContiguousBuffers) where TPixel : struct, IPixel { - static void RunTest(string providerDump) + static void RunTest(string providerDump, string nonContiguousBuffersStr) { TestImageProvider provider = BasicSerializer.Deserialize>(providerDump); + if (!string.IsNullOrEmpty(nonContiguousBuffersStr)) + { + provider.LimitAllocatorBufferCapacity(); + } + using Image image = provider.GetImage(JpegDecoder); - image.DebugSave(provider); + image.DebugSave(provider, testOutputDetails: nonContiguousBuffersStr); provider.Utility.TestName = DecodeBaselineJpegOutputName; image.CompareToReferenceOutput( @@ -32,7 +39,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg } string providerDump = BasicSerializer.Serialize(provider); - RemoteExecutor.Invoke(RunTest, providerDump).Dispose(); + RemoteExecutor.Invoke( + RunTest, + providerDump, + enforceNonContiguousBuffers ? "NonContiguous" : string.Empty) + .Dispose(); } [Theory] diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Progressive.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Progressive.cs index 886cef7ac..33bf64bb4 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Progressive.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Progressive.cs @@ -14,17 +14,23 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg public const string DecodeProgressiveJpegOutputName = "DecodeProgressiveJpeg"; [Theory] - [WithFileCollection(nameof(ProgressiveTestJpegs), PixelTypes.Rgba32)] - public void DecodeProgressiveJpeg(TestImageProvider provider) + [WithFileCollection(nameof(ProgressiveTestJpegs), PixelTypes.Rgba32, false)] + [WithFile(TestImages.Jpeg.Progressive.Progress, PixelTypes.Rgba32, true)] + public void DecodeProgressiveJpeg(TestImageProvider provider, bool enforceNonContiguousBuffers) where TPixel : struct, IPixel { - static void RunTest(string providerDump) + static void RunTest(string providerDump, string nonContiguousBuffersStr) { TestImageProvider provider = BasicSerializer.Deserialize>(providerDump); + if (!string.IsNullOrEmpty(nonContiguousBuffersStr)) + { + provider.LimitAllocatorBufferCapacity(); + } + using Image image = provider.GetImage(JpegDecoder); - image.DebugSave(provider); + image.DebugSave(provider, nonContiguousBuffersStr); provider.Utility.TestName = DecodeProgressiveJpegOutputName; image.CompareToReferenceOutput( @@ -33,8 +39,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg appendPixelTypeToFileName: false); } - string dump = BasicSerializer.Serialize(provider); - RemoteExecutor.Invoke(RunTest, dump).Dispose(); + string providerDump = BasicSerializer.Serialize(provider); + RemoteExecutor.Invoke( + RunTest, + providerDump, + enforceNonContiguousBuffers ? "NonContiguous" : string.Empty); } } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs index 09e98b5c4..d829b5f98 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs @@ -95,19 +95,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg public void JpegDecoder_IsNotBoundToSinglePixelType(TestImageProvider provider) where TPixel : struct, IPixel { - static void RunTest(string providerDump) - { - TestImageProvider provider = - BasicSerializer.Deserialize>(providerDump); - using Image image = provider.GetImage(JpegDecoder); - image.DebugSave(provider); - - provider.Utility.TestName = DecodeBaselineJpegOutputName; - image.CompareToReferenceOutput(ImageComparer.Tolerant(BaselineTolerance), provider, appendPixelTypeToFileName: false); - } - - string dump = BasicSerializer.Serialize(provider); - RemoteExecutor.Invoke(RunTest, dump).Dispose(); + using Image image = provider.GetImage(JpegDecoder); + image.DebugSave(provider); + + provider.Utility.TestName = DecodeBaselineJpegOutputName; + image.CompareToReferenceOutput( + ImageComparer.Tolerant(BaselineTolerance), + provider, + appendPixelTypeToFileName: false); } // DEBUG ONLY! diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs index 4dd6ab655..0427b3732 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs @@ -22,14 +22,18 @@ namespace SixLabors.ImageSharp.Tests // are shared between PixelTypes.Color & PixelTypes.Rgba32 private class Key : IEquatable { - private Tuple commonValues; + private readonly Tuple commonValues; - private Dictionary decoderParameters; + private readonly Dictionary decoderParameters; - public Key(PixelTypes pixelType, string filePath, IImageDecoder customDecoder) + public Key(PixelTypes pixelType, string filePath, int allocatorBufferCapacity, IImageDecoder customDecoder) { Type customType = customDecoder?.GetType(); - this.commonValues = new Tuple(pixelType, filePath, customType); + this.commonValues = new Tuple( + pixelType, + filePath, + customType, + allocatorBufferCapacity); this.decoderParameters = GetDecoderParameters(customDecoder); } @@ -147,12 +151,8 @@ namespace SixLabors.ImageSharp.Tests { Guard.NotNull(decoder, nameof(decoder)); - if (!TestEnvironment.Is64BitProcess) - { - return this.LoadImage(decoder); - } - - var key = new Key(this.PixelType, this.FilePath, decoder); + int bufferCapacity = this.Configuration.MemoryAllocator.GetBufferCapacityInBytes(); + 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/TestImageExtensions.cs b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs index 8cf53bf85..d4c2dc307 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs @@ -5,7 +5,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Numerics; - +using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced.ParallelUtils; using SixLabors.ImageSharp.Formats; @@ -666,6 +666,15 @@ namespace SixLabors.ImageSharp.Tests } } + internal static void LimitAllocatorBufferCapacity( + this TestImageProvider provider, + int bufferCapacityInPixels = 40000) // 200 x 200 + where TPixel : struct, IPixel + { + var allocator = (ArrayPoolMemoryAllocator)provider.Configuration.MemoryAllocator; + allocator.BufferCapacityInBytes = Unsafe.SizeOf() * bufferCapacityInPixels; + } + internal static Image ToGrayscaleImage(this Buffer2D buffer, float scale) { var image = new Image(buffer.Width, buffer.Height); From c2cecdfff8ce2843a0078a0da2afd366c710327c Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Wed, 5 Feb 2020 01:15:51 +0100 Subject: [PATCH 21/62] jpeg encoder tests for disco buffers --- .../Formats/Jpg/JpegDecoderTests.Baseline.cs | 4 +- .../Jpg/JpegDecoderTests.Progressive.cs | 4 +- .../Formats/Jpg/JpegEncoderTests.cs | 85 +++++++++++-------- 3 files changed, 55 insertions(+), 38 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Baseline.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Baseline.cs index 69c0aa87a..dc4a56195 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Baseline.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Baseline.cs @@ -15,7 +15,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [Theory] [WithFileCollection(nameof(BaselineTestJpegs), PixelTypes.Rgba32, false)] [WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgba32, true)] - public void DecodeBaselineJpeg(TestImageProvider provider, bool enforceNonContiguousBuffers) + public void DecodeBaselineJpeg(TestImageProvider provider, bool enforceDiscontiguousBuffers) where TPixel : struct, IPixel { static void RunTest(string providerDump, string nonContiguousBuffersStr) @@ -42,7 +42,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg RemoteExecutor.Invoke( RunTest, providerDump, - enforceNonContiguousBuffers ? "NonContiguous" : string.Empty) + enforceDiscontiguousBuffers ? "Disco" : string.Empty) .Dispose(); } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Progressive.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Progressive.cs index 33bf64bb4..0755f79d1 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Progressive.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Progressive.cs @@ -16,7 +16,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [Theory] [WithFileCollection(nameof(ProgressiveTestJpegs), PixelTypes.Rgba32, false)] [WithFile(TestImages.Jpeg.Progressive.Progress, PixelTypes.Rgba32, true)] - public void DecodeProgressiveJpeg(TestImageProvider provider, bool enforceNonContiguousBuffers) + public void DecodeProgressiveJpeg(TestImageProvider provider, bool enforceDiscontiguousBuffers) where TPixel : struct, IPixel { static void RunTest(string providerDump, string nonContiguousBuffersStr) @@ -43,7 +43,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg RemoteExecutor.Invoke( RunTest, providerDump, - enforceNonContiguousBuffers ? "NonContiguous" : string.Empty); + enforceDiscontiguousBuffers ? "Disco" : string.Empty); } } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs index f7acb9fca..0000ef13f 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs @@ -15,30 +15,30 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg public class JpegEncoderTests { public static readonly TheoryData QualityFiles = - new TheoryData - { - { TestImages.Jpeg.Baseline.Calliphora, 80 }, - { TestImages.Jpeg.Progressive.Fb, 75 } - }; + new TheoryData + { + { TestImages.Jpeg.Baseline.Calliphora, 80 }, + { TestImages.Jpeg.Progressive.Fb, 75 } + }; public static readonly TheoryData BitsPerPixel_Quality = - new TheoryData - { - { JpegSubsample.Ratio420, 40 }, - { JpegSubsample.Ratio420, 60 }, - { JpegSubsample.Ratio420, 100 }, - { JpegSubsample.Ratio444, 40 }, - { JpegSubsample.Ratio444, 60 }, - { JpegSubsample.Ratio444, 100 }, - }; + new TheoryData + { + { JpegSubsample.Ratio420, 40 }, + { JpegSubsample.Ratio420, 60 }, + { JpegSubsample.Ratio420, 100 }, + { JpegSubsample.Ratio444, 40 }, + { JpegSubsample.Ratio444, 60 }, + { JpegSubsample.Ratio444, 100 }, + }; public static readonly TheoryData RatioFiles = - new TheoryData - { - { TestImages.Jpeg.Baseline.Ratio1x1, 1, 1, PixelResolutionUnit.AspectRatio }, - { TestImages.Jpeg.Baseline.Snake, 300, 300, PixelResolutionUnit.PixelsPerInch }, - { TestImages.Jpeg.Baseline.GammaDalaiLamaGray, 72, 72, PixelResolutionUnit.PixelsPerInch } - }; + new TheoryData + { + { TestImages.Jpeg.Baseline.Ratio1x1, 1, 1, PixelResolutionUnit.AspectRatio }, + { TestImages.Jpeg.Baseline.Snake, 300, 300, PixelResolutionUnit.PixelsPerInch }, + { TestImages.Jpeg.Baseline.GammaDalaiLamaGray, 72, 72, PixelResolutionUnit.PixelsPerInch } + }; [Theory] [MemberData(nameof(QualityFiles))] @@ -71,6 +71,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [WithTestPatternImages(nameof(BitsPerPixel_Quality), 51, 7, PixelTypes.Rgba32)] [WithSolidFilledImages(nameof(BitsPerPixel_Quality), 1, 1, 255, 100, 50, 255, PixelTypes.Rgba32)] [WithTestPatternImages(nameof(BitsPerPixel_Quality), 7, 5, PixelTypes.Rgba32)] + [WithTestPatternImages(nameof(BitsPerPixel_Quality), 600, 400, PixelTypes.Rgba32)] public void EncodeBaseline_WorksWithDifferentSizes(TestImageProvider provider, JpegSubsample subsample, int quality) where TPixel : struct, IPixel => TestJpegEncoderCore(provider, subsample, quality); @@ -79,6 +80,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg public void EncodeBaseline_IsNotBoundToSinglePixelType(TestImageProvider provider, JpegSubsample subsample, int quality) where TPixel : struct, IPixel => TestJpegEncoderCore(provider, subsample, quality); + [Theory] + [WithTestPatternImages(nameof(BitsPerPixel_Quality), 600, 400, PixelTypes.Rgba32)] + public void EncodeBaseline_WorksWithDiscontiguousBuffers(TestImageProvider provider, JpegSubsample subsample, int quality) + where TPixel : struct, IPixel => TestJpegEncoderCore(provider, subsample, quality, true, ImageComparer.TolerantPercentage(0.1f)); + /// /// Anton's SUPER-SCIENTIFIC tolerance threshold calculation /// @@ -105,25 +111,36 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg private static void TestJpegEncoderCore( TestImageProvider provider, JpegSubsample subsample, - int quality = 100) + int quality = 100, + bool enforceDiscontiguousBuffers = false, + ImageComparer comparer = null) where TPixel : struct, IPixel { - using (Image image = provider.GetImage()) + if (enforceDiscontiguousBuffers) { - // There is no alpha in Jpeg! - image.Mutate(c => c.MakeOpaque()); + provider.LimitAllocatorBufferCapacity(); + } - var encoder = new JpegEncoder - { - Subsample = subsample, - Quality = quality - }; - string info = $"{subsample}-Q{quality}"; - ImageComparer comparer = GetComparer(quality, subsample); - - // Does DebugSave & load reference CompareToReferenceInput(): - image.VerifyEncoder(provider, "jpeg", info, encoder, comparer, referenceImageExtension: "png"); + using Image image = provider.GetImage(); + + // There is no alpha in Jpeg! + image.Mutate(c => c.MakeOpaque()); + + var encoder = new JpegEncoder + { + Subsample = subsample, + Quality = quality + }; + string info = $"{subsample}-Q{quality}"; + if (enforceDiscontiguousBuffers) + { + info += "-Disco"; } + + comparer ??= GetComparer(quality, subsample); + + // Does DebugSave & load reference CompareToReferenceInput(): + image.VerifyEncoder(provider, "jpeg", info, encoder, comparer, referenceImageExtension: "png"); } [Fact] From f99ead64c27177f36c8fe0689ec39f91a899b4fe Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Fri, 7 Feb 2020 02:59:49 +0100 Subject: [PATCH 22/62] fix JpegEncoder disco buffer handling --- .../Encoder/YCbCrForwardConverter{TPixel}.cs | 4 +- .../Jpeg/Components/GenericBlock8x8.cs | 36 ++++---- .../Formats/Jpeg/Components/RowOctet.cs | 60 ++++++++++++++ .../Formats/Jpeg/JpegEncoderCore.cs | 14 +++- .../Formats/Jpg/GenericBlock8x8Tests.cs | 6 +- .../Formats/Jpg/JpegEncoderTests.cs | 28 ++++--- .../ImageProviders/SolidProvider.cs | 3 +- .../ReferenceCodecs/MagickReferenceDecoder.cs | 83 ++++++++++++------- .../TestUtilities/TestImageExtensions.cs | 6 +- 9 files changed, 167 insertions(+), 73 deletions(-) create mode 100644 src/ImageSharp/Formats/Jpeg/Components/RowOctet.cs diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs index 92482de2a..9619a78fc 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs @@ -55,9 +55,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder /// /// 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) + public void Convert(ImageFrame frame, int x, int y, in RowOctet currentRows) { - this.pixelBlock.LoadAndStretchEdges(frame, x, y); + this.pixelBlock.LoadAndStretchEdges(frame.PixelBuffer, x, y, currentRows); Span rgbSpan = this.rgbBlock.AsSpanUnsafe(); PixelOperations.Instance.ToRgb24(frame.GetConfiguration(), this.pixelBlock.AsSpanUnsafe(), rgbSpan); diff --git a/src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.cs b/src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.cs index 3d1e22a99..ebc071494 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.cs @@ -54,24 +54,24 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components set => this[(y * 8) + x] = value; } - public void LoadAndStretchEdges(IPixelSource source, int sourceX, int sourceY) - where TPixel : struct, IPixel - { - if (source.PixelBuffer is Buffer2D buffer) - { - this.LoadAndStretchEdges(buffer, sourceX, sourceY); - } - else - { - throw new InvalidOperationException("LoadAndStretchEdges() is only valid for TPixel == T !"); - } - } + // public void LoadAndStretchEdges(IPixelSource source, int sourceX, RowOctet currentRows) + // where TPixel : struct, IPixel + // { + // if (source.PixelBuffer is Buffer2D buffer) + // { + // this.LoadAndStretchEdges(buffer, sourceX, sourceY); + // } + // else + // { + // throw new InvalidOperationException("LoadAndStretchEdges() is only valid for TPixel == T !"); + // } + // } /// /// 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) + public void LoadAndStretchEdges(Buffer2D source, int sourceX, int sourceY, in RowOctet currentRows) { int width = Math.Min(8, source.Width - sourceX); int height = Math.Min(8, source.Height - sourceY); @@ -85,15 +85,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components int remainderXCount = 8 - width; ref byte blockStart = ref Unsafe.As, byte>(ref this); - ref byte imageStart = ref Unsafe.As( - ref Unsafe.Add(ref MemoryMarshal.GetReference(source.GetRowSpan(sourceY)), sourceX)); - int blockRowSizeInBytes = 8 * Unsafe.SizeOf(); - int imageRowSizeInBytes = source.Width * Unsafe.SizeOf(); for (int y = 0; y < height; y++) { - ref byte s = ref Unsafe.Add(ref imageStart, y * imageRowSizeInBytes); + 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); @@ -127,4 +125,4 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components /// public Span AsSpanUnsafe() => new Span(Unsafe.AsPointer(ref this), Size); } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/RowOctet.cs b/src/ImageSharp/Formats/Jpeg/Components/RowOctet.cs new file mode 100644 index 000000000..57a134703 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/RowOctet.cs @@ -0,0 +1,60 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Formats.Jpeg.Components +{ + /// + /// Cache 8 pixel rows on the stack, which may originate from different buffers of a . + /// + [StructLayout(LayoutKind.Sequential)] + internal readonly ref struct RowOctet + where T : struct + { + private readonly Span row0; + private readonly Span row1; + private readonly Span row2; + private readonly Span row3; + private readonly Span row4; + private readonly Span row5; + private readonly Span row6; + private readonly Span row7; + + public RowOctet(Buffer2D buffer, int startY) + { + int y = startY; + int height = buffer.Height; + this.row0 = y < height ? buffer.GetRowSpan(y++) : default; + this.row1 = y < height ? buffer.GetRowSpan(y++) : default; + this.row2 = y < height ? buffer.GetRowSpan(y++) : default; + this.row3 = y < height ? buffer.GetRowSpan(y++) : default; + this.row4 = y < height ? buffer.GetRowSpan(y++) : default; + this.row5 = y < height ? buffer.GetRowSpan(y++) : default; + this.row6 = y < height ? buffer.GetRowSpan(y++) : default; + this.row7 = y < height ? buffer.GetRowSpan(y) : default; + } + + public Span this[int y] + { + get + { + // No unsafe tricks, since Span can't be used as a generic argument + return y switch + { + 0 => this.row0, + 1 => this.row1, + 2 => this.row2, + 3 => this.row3, + 4 => this.row4, + 5 => this.row5, + 6 => this.row6, + 7 => this.row7, + _ => throw new IndexOutOfRangeException() + }; + } + } + } +} diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs index cd3c19aa3..dcf2d72a5 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs @@ -9,6 +9,7 @@ using SixLabors.ImageSharp.Common.Helpers; using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; using SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.Metadata.Profiles.Icc; @@ -409,12 +410,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg int prevDCY = 0, prevDCCb = 0, prevDCCr = 0; var pixelConverter = YCbCrForwardConverter.Create(); + ImageFrame frame = pixels.Frames.RootFrame; + Buffer2D pixelBuffer = frame.PixelBuffer; for (int y = 0; y < pixels.Height; y += 8) { + var currentRows = new RowOctet(pixelBuffer, y); + for (int x = 0; x < pixels.Width; x += 8) { - pixelConverter.Convert(pixels.Frames.RootFrame, x, y); + pixelConverter.Convert(frame, x, y, currentRows); prevDCY = this.WriteBlock( QuantIndex.Luminance, @@ -935,6 +940,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg // ReSharper disable once InconsistentNaming int prevDCY = 0, prevDCCb = 0, prevDCCr = 0; + ImageFrame frame = pixels.Frames.RootFrame; + Buffer2D pixelBuffer = frame.PixelBuffer; for (int y = 0; y < pixels.Height; y += 16) { @@ -945,7 +952,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg int xOff = (i & 1) * 8; int yOff = (i & 2) * 4; - pixelConverter.Convert(pixels.Frames.RootFrame, x + xOff, y + yOff); + // TODO: Try pushing this to the outer loop! + var currentRows = new RowOctet(pixelBuffer, y + yOff); + + pixelConverter.Convert(frame, x + xOff, y + yOff, currentRows); cbPtr[i] = pixelConverter.Cb; crPtr[i] = pixelConverter.Cr; diff --git a/tests/ImageSharp.Tests/Formats/Jpg/GenericBlock8x8Tests.cs b/tests/ImageSharp.Tests/Formats/Jpg/GenericBlock8x8Tests.cs index 7c42af596..38b33e842 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/GenericBlock8x8Tests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/GenericBlock8x8Tests.cs @@ -41,7 +41,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg using (Image s = provider.GetImage()) { var d = default(GenericBlock8x8); - d.LoadAndStretchEdges(s.Frames.RootFrame, 0, 0); + var rowOctet = new RowOctet(s.GetRootFramePixelBuffer(), 0); + d.LoadAndStretchEdges(s.Frames.RootFrame.PixelBuffer, 0, 0, rowOctet); TPixel a = s.Frames.RootFrame[0, 0]; TPixel b = d[0, 0]; @@ -65,7 +66,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg using (Image s = provider.GetImage()) { var d = default(GenericBlock8x8); - d.LoadAndStretchEdges(s.Frames.RootFrame, 6, 7); + var rowOctet = new RowOctet(s.GetRootFramePixelBuffer(), 7); + d.LoadAndStretchEdges(s.Frames.RootFrame.PixelBuffer, 6, 7, rowOctet); Assert.Equal(s[6, 7], d[0, 0]); Assert.Equal(s[6, 8], d[0, 1]); diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs index 0000ef13f..49ef7f8f8 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs @@ -3,6 +3,7 @@ using System.IO; using SixLabors.ImageSharp.Formats.Jpeg; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; @@ -81,9 +82,20 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg where TPixel : struct, IPixel => TestJpegEncoderCore(provider, subsample, quality); [Theory] - [WithTestPatternImages(nameof(BitsPerPixel_Quality), 600, 400, PixelTypes.Rgba32)] - public void EncodeBaseline_WorksWithDiscontiguousBuffers(TestImageProvider provider, JpegSubsample subsample, int quality) - where TPixel : struct, IPixel => TestJpegEncoderCore(provider, subsample, quality, true, ImageComparer.TolerantPercentage(0.1f)); + [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32, JpegSubsample.Ratio444)] + [WithTestPatternImages(587, 821, PixelTypes.Rgba32, JpegSubsample.Ratio444)] + [WithTestPatternImages(677, 683, PixelTypes.Bgra32, JpegSubsample.Ratio420)] + [WithSolidFilledImages(400, 400, "Red", PixelTypes.Bgr24, JpegSubsample.Ratio420)] + public void EncodeBaseline_WorksWithDiscontiguousBuffers(TestImageProvider provider, JpegSubsample subsample) + where TPixel : struct, IPixel + { + ImageComparer comparer = subsample == JpegSubsample.Ratio444 + ? ImageComparer.TolerantPercentage(0.1f) + : ImageComparer.TolerantPercentage(5f); + + provider.LimitAllocatorBufferCapacity(); + TestJpegEncoderCore(provider, subsample, 100, comparer); + } /// /// Anton's SUPER-SCIENTIFIC tolerance threshold calculation @@ -112,15 +124,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg TestImageProvider provider, JpegSubsample subsample, int quality = 100, - bool enforceDiscontiguousBuffers = false, ImageComparer comparer = null) where TPixel : struct, IPixel { - if (enforceDiscontiguousBuffers) - { - provider.LimitAllocatorBufferCapacity(); - } - using Image image = provider.GetImage(); // There is no alpha in Jpeg! @@ -132,10 +138,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg Quality = quality }; string info = $"{subsample}-Q{quality}"; - if (enforceDiscontiguousBuffers) - { - info += "-Disco"; - } comparer ??= GetComparer(quality, subsample); diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/SolidProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/SolidProvider.cs index 85506a9de..179680e1a 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/SolidProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/SolidProvider.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using Xunit.Abstractions; @@ -53,7 +54,7 @@ namespace SixLabors.ImageSharp.Tests Image image = base.GetImage(); Color color = new Rgba32(this.r, this.g, this.b, this.a); - image.GetPixelSpan().Fill(color.ToPixel()); + image.GetRootFramePixelBuffer().MemoryGroup.Fill(color.ToPixel()); return image; } diff --git a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs index 58afd48a7..e492efb25 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs @@ -3,12 +3,14 @@ using System; using System.IO; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using ImageMagick; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Formats; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs @@ -17,45 +19,64 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs { public static MagickReferenceDecoder Instance { get; } = new MagickReferenceDecoder(); + private static void FromRgba32Bytes(Configuration configuration, Span rgbaBytes, IMemoryGroup destinationGroup) + where TPixel : struct, IPixel + { + foreach (Memory m in destinationGroup) + { + Span destBuffer = m.Span; + PixelOperations.Instance.FromRgba32Bytes( + configuration, + rgbaBytes, + destBuffer, + destBuffer.Length); + rgbaBytes = rgbaBytes.Slice(destBuffer.Length * 4); + } + } + + private static void FromRgba64Bytes(Configuration configuration, Span rgbaBytes, IMemoryGroup destinationGroup) + where TPixel : struct, IPixel + { + foreach (Memory m in destinationGroup) + { + Span destBuffer = m.Span; + PixelOperations.Instance.FromRgba64Bytes( + configuration, + rgbaBytes, + destBuffer, + destBuffer.Length); + rgbaBytes = rgbaBytes.Slice(destBuffer.Length * 8); + } + } + public Image Decode(Configuration configuration, Stream stream) where TPixel : struct, IPixel { - using (var magickImage = new MagickImage(stream)) + using var magickImage = new MagickImage(stream); + var result = new Image(configuration, magickImage.Width, magickImage.Height); + MemoryGroup resultPixels = result.GetRootFramePixelBuffer().MemoryGroup; + + using (IPixelCollection pixels = magickImage.GetPixelsUnsafe()) { - var result = new Image(configuration, magickImage.Width, magickImage.Height); - Span resultPixels = result.GetPixelSpan(); + if (magickImage.Depth == 8) + { + byte[] data = pixels.ToByteArray(PixelMapping.RGBA); - using (IPixelCollection pixels = magickImage.GetPixelsUnsafe()) + FromRgba32Bytes(configuration, data, resultPixels); + } + else if (magickImage.Depth == 16) { - if (magickImage.Depth == 8) - { - byte[] data = pixels.ToByteArray(PixelMapping.RGBA); - - PixelOperations.Instance.FromRgba32Bytes( - configuration, - data, - resultPixels, - resultPixels.Length); - } - else if (magickImage.Depth == 16) - { - ushort[] data = pixels.ToShortArray(PixelMapping.RGBA); - Span bytes = MemoryMarshal.Cast(data.AsSpan()); - - PixelOperations.Instance.FromRgba64Bytes( - configuration, - bytes, - resultPixels, - resultPixels.Length); - } - else - { - throw new InvalidOperationException(); - } + ushort[] data = pixels.ToShortArray(PixelMapping.RGBA); + Span bytes = MemoryMarshal.Cast(data.AsSpan()); + FromRgba64Bytes(configuration, bytes, resultPixels); + } + else + { + throw new InvalidOperationException(); } - - return result; } + + return result; } public Image Decode(Configuration configuration, Stream stream) => this.Decode(configuration, stream); diff --git a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs index d4c2dc307..fa5eab20a 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs @@ -657,12 +657,12 @@ namespace SixLabors.ImageSharp.Tests testOutputDetails, appendPixelTypeToFileName); - referenceDecoder = referenceDecoder ?? TestEnvironment.GetReferenceDecoder(actualOutputFile); + referenceDecoder ??= TestEnvironment.GetReferenceDecoder(actualOutputFile); - using (var actualImage = Image.Load(actualOutputFile, referenceDecoder)) + using (var encodedImage = Image.Load(actualOutputFile, referenceDecoder)) { ImageComparer comparer = customComparer ?? ImageComparer.Exact; - comparer.VerifySimilarity(actualImage, image); + comparer.VerifySimilarity(encodedImage, image); } } From ee502195c2b8ba41d395226a9bc627a17332bc22 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Fri, 7 Feb 2020 03:54:37 +0100 Subject: [PATCH 23/62] change AdvancedImageExtensions public API-s --- .../Advanced/AdvancedImageExtensions.cs | 107 ++++++++---------- .../Advanced/AdvancedImageExtensionsTests.cs | 29 +---- .../Image/ImageTests.WrapMemory.cs | 5 +- 3 files changed, 56 insertions(+), 85 deletions(-) diff --git a/src/ImageSharp/Advanced/AdvancedImageExtensions.cs b/src/ImageSharp/Advanced/AdvancedImageExtensions.cs index 79a863ff4..665d0e28b 100644 --- a/src/ImageSharp/Advanced/AdvancedImageExtensions.cs +++ b/src/ImageSharp/Advanced/AdvancedImageExtensions.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Linq; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Memory; @@ -40,7 +41,7 @@ namespace SixLabors.ImageSharp.Advanced => GetConfiguration((IConfigurationProvider)source); /// - /// Gets the configuration . + /// Gets the configuration. /// /// The source image /// Returns the bounds of the image @@ -48,15 +49,56 @@ namespace SixLabors.ImageSharp.Advanced => source?.Configuration ?? Configuration.Default; /// - /// Gets the representation of the pixels as a of contiguous memory in the source image's pixel format - /// stored in row major order. + /// Gets the representation of the pixels as a containing the backing pixel data of the image + /// stored in row major order, as a list of contiguous blocks in the source image's pixel format. /// + /// The source image. /// The type of the pixel. - /// The source. + /// The . + /// + /// Certain Image Processors may invalidate the returned and all it's buffers, + /// therefore it's not recommended to mutate the image while holding a reference to it's . + /// + public static IMemoryGroup GetPixelMemoryGroup(this ImageFrame source) + where TPixel : struct, IPixel + => source.PixelBuffer.MemoryGroup.View; + + /// + /// Gets the representation of the pixels as a containing the backing pixel data of the image + /// stored in row major order, as a list of contiguous blocks in the source image's pixel format. + /// + /// The source image. + /// The type of the pixel. + /// The . + /// + /// Certain Image Processors may invalidate the returned and all it's buffers, + /// therefore it's not recommended to mutate the image while holding a reference to it's . + /// + public static IMemoryGroup GetPixelMemoryGroup(this Image source) + where TPixel : struct, IPixel + => source.Frames.RootFrame.GetPixelMemoryGroup(); + + /// + /// 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. + /// + /// The type of the pixel. + /// The source image. /// The + /// Thrown when the backing buffer is discontiguous. + [Obsolete( + @"GetPixelSpan might fail, because the backing buffer allowed to be discontiguous for large images. Use GetPixelMemoryGroup or GetPixelRowSpan instead!")] public static Span GetPixelSpan(this ImageFrame source) where TPixel : struct, IPixel - => source.GetPixelMemory().Span; + { + IMemoryGroup mg = source.GetPixelMemoryGroup(); + if (mg.Count > 1) + { + throw new InvalidOperationException($"GetPixelSpan is invalid, since the backing buffer of this {source.Width}x{source.Height} sized image is discontiguos!"); + } + + return mg.Single().Span; + } /// /// Gets the representation of the pixels as a of contiguous memory in the source image's pixel format @@ -65,6 +107,9 @@ namespace SixLabors.ImageSharp.Advanced /// The type of the pixel. /// The source. /// The + /// Thrown when the backing buffer is discontiguous. + [Obsolete( + @"GetPixelSpan might fail, because the backing buffer allowed to be discontiguous for large images. Use GetPixelMemoryGroup or GetPixelRowSpan instead!")] public static Span GetPixelSpan(this Image source) where TPixel : struct, IPixel => source.Frames.RootFrame.GetPixelSpan(); @@ -93,58 +138,6 @@ namespace SixLabors.ImageSharp.Advanced where TPixel : struct, IPixel => source.Frames.RootFrame.GetPixelRowSpan(rowIndex); - /// - /// Returns a reference to the 0th element of the Pixel buffer, - /// allowing direct manipulation of pixel data through unsafe operations. - /// The pixel buffer is a contiguous memory area containing Width*Height TPixel elements laid out in row-major order. - /// - /// The Pixel format. - /// The source image frame - /// A pinnable reference the first root of the pixel buffer. - [Obsolete("This method will be removed in our next release! Please use MemoryMarshal.GetReference(source.GetPixelSpan())!")] - public static ref TPixel DangerousGetPinnableReferenceToPixelBuffer(this ImageFrame source) - where TPixel : struct, IPixel - => ref DangerousGetPinnableReferenceToPixelBuffer((IPixelSource)source); - - /// - /// Returns a reference to the 0th element of the Pixel buffer, - /// allowing direct manipulation of pixel data through unsafe operations. - /// The pixel buffer is a contiguous memory area containing Width*Height TPixel elements laid out in row-major order. - /// - /// The Pixel format. - /// The source image - /// A pinnable reference the first root of the pixel buffer. - [Obsolete("This method will be removed in our next release! Please use MemoryMarshal.GetReference(source.GetPixelSpan())!")] - public static ref TPixel DangerousGetPinnableReferenceToPixelBuffer(this Image source) - where TPixel : struct, IPixel - => ref source.Frames.RootFrame.DangerousGetPinnableReferenceToPixelBuffer(); - - /// - /// Gets the representation of the pixels as a of contiguous memory in the source image's pixel format - /// stored in row major order. - /// - /// The Pixel format. - /// The source - /// The - internal static Memory GetPixelMemory(this ImageFrame source) - where TPixel : struct, IPixel - { - return source.PixelBuffer.GetSingleMemory(); - } - - /// - /// Gets the representation of the pixels as a of contiguous memory in the source image's pixel format - /// stored in row major order. - /// - /// The Pixel format. - /// The source - /// The - internal static Memory GetPixelMemory(this Image source) - where TPixel : struct, IPixel - { - return source.Frames.RootFrame.GetPixelMemory(); - } - /// /// Gets the representation of the pixels as a of contiguous memory /// at row beginning from the the first pixel on that row. diff --git a/tests/ImageSharp.Tests/Advanced/AdvancedImageExtensionsTests.cs b/tests/ImageSharp.Tests/Advanced/AdvancedImageExtensionsTests.cs index f6b51e8c5..548caa488 100644 --- a/tests/ImageSharp.Tests/Advanced/AdvancedImageExtensionsTests.cs +++ b/tests/ImageSharp.Tests/Advanced/AdvancedImageExtensionsTests.cs @@ -4,6 +4,7 @@ using System; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using Xunit; @@ -25,7 +26,7 @@ namespace SixLabors.ImageSharp.Tests.Advanced var targetBuffer = new TPixel[image0.Width * image0.Height]; // Act: - Memory memory = image0.GetPixelMemory(); + Memory memory = image0.GetRootFramePixelBuffer().GetSingleMemory(); // Assert: Assert.Equal(image0.Width * image0.Height, memory.Length); @@ -56,7 +57,7 @@ namespace SixLabors.ImageSharp.Tests.Advanced using (var image1 = Image.WrapMemory(externalMemory, image0.Width, image0.Height)) { - Memory internalMemory = image1.GetPixelMemory(); + Memory internalMemory = image1.GetRootFramePixelBuffer().GetSingleMemory(); Assert.Equal(targetBuffer.Length, internalMemory.Length); Assert.True(Unsafe.AreSame(ref targetBuffer[0], ref internalMemory.Span[0])); @@ -120,29 +121,5 @@ namespace SixLabors.ImageSharp.Tests.Advanced } } } - - #pragma warning disable 0618 - - [Theory] - [WithTestPatternImages(131, 127, PixelTypes.Rgba32 | PixelTypes.Bgr24)] - public unsafe void DangerousGetPinnableReference_CopyToBuffer(TestImageProvider provider) - where TPixel : struct, IPixel - { - using (Image image = provider.GetImage()) - { - var targetBuffer = new TPixel[image.Width * image.Height]; - - ref byte source = ref Unsafe.As(ref targetBuffer[0]); - ref byte dest = ref Unsafe.As(ref image.DangerousGetPinnableReferenceToPixelBuffer()); - fixed (byte* targetPtr = &source) - fixed (byte* pixelBasePtr = &dest) - { - uint dataSizeInBytes = (uint)(image.Width * image.Height * Unsafe.SizeOf()); - Unsafe.CopyBlock(targetPtr, pixelBasePtr, dataSizeInBytes); - } - - image.ComparePixelBufferTo(targetBuffer); - } - } } } diff --git a/tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs b/tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs index 0cf3071a0..423309dfb 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs @@ -9,6 +9,7 @@ using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Common.Helpers; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; using Xunit; @@ -116,7 +117,7 @@ namespace SixLabors.ImageSharp.Tests using (var image = Image.WrapMemory(memory, bmp.Width, bmp.Height)) { - Assert.Equal(memory, image.GetPixelMemory()); + Assert.Equal(memory, image.GetRootFramePixelBuffer().GetSingleMemory()); image.GetPixelSpan().Fill(bg); for (var i = 10; i < 20; i++) { @@ -151,7 +152,7 @@ namespace SixLabors.ImageSharp.Tests using (var image = Image.WrapMemory(memoryManager, bmp.Width, bmp.Height)) { - Assert.Equal(memoryManager.Memory, image.GetPixelMemory()); + Assert.Equal(memoryManager.Memory, image.GetRootFramePixelBuffer().GetSingleMemory()); image.GetPixelSpan().Fill(bg); for (var i = 10; i < 20; i++) From 793a4fb6c13a814f48cb2078a3a0e4a05048d460 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Fri, 7 Feb 2020 23:38:26 +0100 Subject: [PATCH 24/62] implement correct AdvancedImageExtensions behavior --- .../Advanced/AdvancedImageExtensions.cs | 20 +- src/ImageSharp/Memory/Buffer2D{T}.cs | 23 ++- .../Advanced/AdvancedImageExtensionsTests.cs | 173 +++++++++++------- .../DiscontiguousBuffers/MemoryGroupIndex.cs | 8 +- .../BasicTestPatternProvider.cs | 56 ++++-- 5 files changed, 169 insertions(+), 111 deletions(-) diff --git a/src/ImageSharp/Advanced/AdvancedImageExtensions.cs b/src/ImageSharp/Advanced/AdvancedImageExtensions.cs index 665d0e28b..fc6ba10b0 100644 --- a/src/ImageSharp/Advanced/AdvancedImageExtensions.cs +++ b/src/ImageSharp/Advanced/AdvancedImageExtensions.cs @@ -87,7 +87,7 @@ namespace SixLabors.ImageSharp.Advanced /// The /// Thrown when the backing buffer is discontiguous. [Obsolete( - @"GetPixelSpan might fail, because the backing buffer allowed to be discontiguous for large images. Use GetPixelMemoryGroup or GetPixelRowSpan instead!")] + @"GetPixelSpan might fail, because the backing buffer could be discontiguous for large images. Use GetPixelMemoryGroup or GetPixelRowSpan instead!")] public static Span GetPixelSpan(this ImageFrame source) where TPixel : struct, IPixel { @@ -109,7 +109,7 @@ namespace SixLabors.ImageSharp.Advanced /// The /// Thrown when the backing buffer is discontiguous. [Obsolete( - @"GetPixelSpan might fail, because the backing buffer allowed to be discontiguous for large images. Use GetPixelMemoryGroup or GetPixelRowSpan instead!")] + @"GetPixelSpan might fail, because the backing buffer could be discontiguous for large images. Use GetPixelMemoryGroup or GetPixelRowSpan instead!")] public static Span GetPixelSpan(this Image source) where TPixel : struct, IPixel => source.Frames.RootFrame.GetPixelSpan(); @@ -146,9 +146,9 @@ namespace SixLabors.ImageSharp.Advanced /// The source. /// The row. /// The - internal static Memory GetPixelRowMemory(this ImageFrame source, int rowIndex) + public static Memory GetPixelRowMemory(this ImageFrame source, int rowIndex) where TPixel : struct, IPixel - => source.PixelBuffer.GetRowMemory(rowIndex); + => source.PixelBuffer.GetRowMemorySafe(rowIndex); /// /// Gets the representation of the pixels as of of contiguous memory @@ -158,7 +158,7 @@ namespace SixLabors.ImageSharp.Advanced /// The source. /// The row. /// The - internal static Memory GetPixelRowMemory(this Image source, int rowIndex) + public static Memory GetPixelRowMemory(this Image source, int rowIndex) where TPixel : struct, IPixel => source.Frames.RootFrame.GetPixelRowMemory(rowIndex); @@ -169,15 +169,5 @@ namespace SixLabors.ImageSharp.Advanced /// Returns the configuration. internal static MemoryAllocator GetMemoryAllocator(this IConfigurationProvider source) => GetConfiguration(source).MemoryAllocator; - - /// - /// Returns a reference to the 0th element of the Pixel buffer. - /// Such a reference can be used for pinning but must never be dereferenced. - /// - /// The source image frame - /// A reference to the element. - private static ref TPixel DangerousGetPinnableReferenceToPixelBuffer(IPixelSource source) - where TPixel : struct, IPixel - => ref MemoryMarshal.GetReference(source.PixelBuffer.GetSingleSpan()); } } diff --git a/src/ImageSharp/Memory/Buffer2D{T}.cs b/src/ImageSharp/Memory/Buffer2D{T}.cs index ea2568efd..bb00b48cd 100644 --- a/src/ImageSharp/Memory/Buffer2D{T}.cs +++ b/src/ImageSharp/Memory/Buffer2D{T}.cs @@ -83,13 +83,24 @@ namespace SixLabors.ImageSharp.Memory : this.GetRowMemorySlow(y).Span; } + /// + /// Disposes the instance + /// + public void Dispose() + { + this.MemoryGroup.Dispose(); + this.cachedMemory = default; + } + /// /// 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(MethodImplOptions.AggressiveInlining)] - public Memory GetRowMemory(int y) + internal Memory GetRowMemoryFast(int y) { return this.cachedMemory.Length > 0 ? this.cachedMemory.Slice(y * this.Width, this.Width) @@ -97,13 +108,11 @@ namespace SixLabors.ImageSharp.Memory } /// - /// Disposes the instance + /// Gets a to the row 'y' beginning from the pixel at the first pixel on that row. /// - public void Dispose() - { - this.MemoryGroup.Dispose(); - this.cachedMemory = default; - } + /// The y (row) coordinate. + /// The . + internal Memory GetRowMemorySafe(int y) => this.MemoryGroup.View.GetBoundedSlice(y * this.Width, this.Width); /// /// Swaps the contents of 'destination' with 'source' if the buffers are owned (1), diff --git a/tests/ImageSharp.Tests/Advanced/AdvancedImageExtensionsTests.cs b/tests/ImageSharp.Tests/Advanced/AdvancedImageExtensionsTests.cs index 548caa488..de69d7207 100644 --- a/tests/ImageSharp.Tests/Advanced/AdvancedImageExtensionsTests.cs +++ b/tests/ImageSharp.Tests/Advanced/AdvancedImageExtensionsTests.cs @@ -2,10 +2,13 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Linq; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers; using Xunit; // ReSharper disable InconsistentNaming @@ -13,113 +16,145 @@ namespace SixLabors.ImageSharp.Tests.Advanced { public class AdvancedImageExtensionsTests { - public class GetPixelMemory + public class GetPixelMemoryGroup { [Theory] - [WithSolidFilledImages(1, 1, "Red", PixelTypes.Rgba32)] - [WithTestPatternImages(131, 127, PixelTypes.Rgba32 | PixelTypes.Bgr24)] - public void WhenMemoryIsOwned(TestImageProvider provider) + [WithBasicTestPatternImages(1, 1, PixelTypes.Rgba32)] + [WithBasicTestPatternImages(131, 127, PixelTypes.Rgba32)] + [WithBasicTestPatternImages(333, 555, PixelTypes.Bgr24)] + public void OwnedMemory_PixelDataIsCorrect(TestImageProvider provider) where TPixel : struct, IPixel { - using (Image image0 = provider.GetImage()) - { - var targetBuffer = new TPixel[image0.Width * image0.Height]; + provider.LimitAllocatorBufferCapacity(); - // Act: - Memory memory = image0.GetRootFramePixelBuffer().GetSingleMemory(); + using Image image = provider.GetImage(); - // Assert: - Assert.Equal(image0.Width * image0.Height, memory.Length); - memory.Span.CopyTo(targetBuffer); + // Act: + IMemoryGroup memoryGroup = image.GetPixelMemoryGroup(); - using (Image image1 = provider.GetImage()) - { - // We are using a copy of the original image for assertion - image1.ComparePixelBufferTo(targetBuffer); - } - } + // Assert: + VerifyMemoryGroupDataMatchesTestPattern(provider, memoryGroup, image.Size()); } [Theory] - [WithSolidFilledImages(1, 1, "Red", PixelTypes.Rgba32 | PixelTypes.Bgr24)] - [WithTestPatternImages(131, 127, PixelTypes.Rgba32 | PixelTypes.Bgr24)] - public void WhenMemoryIsConsumed(TestImageProvider provider) + [WithBlankImages(16, 16, PixelTypes.Rgba32)] + public void OwnedMemory_DestructiveMutate_ShouldInvalidateMemoryGroup(TestImageProvider provider) where TPixel : struct, IPixel { - using (Image image0 = provider.GetImage()) - { - var targetBuffer = new TPixel[image0.Width * image0.Height]; - image0.GetPixelSpan().CopyTo(targetBuffer); + using Image image = provider.GetImage(); + + IMemoryGroup memoryGroup = image.GetPixelMemoryGroup(); + Memory memory = memoryGroup.Single(); - var managerOfExternalMemory = new TestMemoryManager(targetBuffer); + image.Mutate(c => c.Resize(8, 8)); - Memory externalMemory = managerOfExternalMemory.Memory; + Assert.False(memoryGroup.IsValid); + Assert.ThrowsAny(() => _ = memoryGroup.First()); + Assert.ThrowsAny(() => _ = memory.Span); + } + + [Theory] + [WithBasicTestPatternImages(1, 1, PixelTypes.Rgba32)] + [WithBasicTestPatternImages(131, 127, PixelTypes.Bgr24)] + public void ConsumedMemory_PixelDataIsCorrect(TestImageProvider provider) + where TPixel : struct, IPixel + { + using Image image0 = provider.GetImage(); + var targetBuffer = new TPixel[image0.Width * image0.Height]; + image0.GetPixelSpan().CopyTo(targetBuffer); - using (var image1 = Image.WrapMemory(externalMemory, image0.Width, image0.Height)) - { - Memory internalMemory = image1.GetRootFramePixelBuffer().GetSingleMemory(); - Assert.Equal(targetBuffer.Length, internalMemory.Length); - Assert.True(Unsafe.AreSame(ref targetBuffer[0], ref internalMemory.Span[0])); + var managerOfExternalMemory = new TestMemoryManager(targetBuffer); - image0.ComparePixelBufferTo(internalMemory.Span); - } + Memory externalMemory = managerOfExternalMemory.Memory; - // Make sure externalMemory works after destruction: - image0.ComparePixelBufferTo(externalMemory.Span); + using (var image1 = Image.WrapMemory(externalMemory, image0.Width, image0.Height)) + { + VerifyMemoryGroupDataMatchesTestPattern(provider, image1.GetPixelMemoryGroup(), image1.Size()); } + + // Make sure externalMemory works after destruction: + VerifyMemoryGroupDataMatchesTestPattern(provider, image0.GetPixelMemoryGroup(), image0.Size()); } - } - [Theory] - [WithSolidFilledImages(1, 1, "Red", PixelTypes.Rgba32)] - [WithTestPatternImages(131, 127, PixelTypes.Rgba32 | PixelTypes.Bgr24)] - public void GetPixelRowMemory(TestImageProvider provider) - where TPixel : struct, IPixel - { - using (Image image = provider.GetImage()) + private static void VerifyMemoryGroupDataMatchesTestPattern( + TestImageProvider provider, + IMemoryGroup memoryGroup, + Size size) + where TPixel : struct, IPixel { - var targetBuffer = new TPixel[image.Width * image.Height]; + Assert.True(memoryGroup.IsValid); + Assert.Equal(size.Width * size.Height, memoryGroup.TotalLength); + Assert.True(memoryGroup.BufferLength % size.Width == 0); - // Act: - for (int y = 0; y < image.Height; y++) + int cnt = 0; + for (MemoryGroupIndex i = memoryGroup.MaxIndex(); i < memoryGroup.MaxIndex(); i += 1, cnt++) { - Memory rowMemory = image.GetPixelRowMemory(y); - rowMemory.Span.CopyTo(targetBuffer.AsSpan(image.Width * y)); - } + int y = cnt / size.Width; + int x = cnt % size.Width; - // Assert: - using (Image image1 = provider.GetImage()) - { - // We are using a copy of the original image for assertion - image1.ComparePixelBufferTo(targetBuffer); + TPixel expected = provider.GetExpectedBasicTestPatternPixelAt(x, y); + TPixel actual = memoryGroup.GetElementAt(i); + Assert.Equal(expected, actual); } } } [Theory] - [WithSolidFilledImages(1, 1, "Red", PixelTypes.Rgba32)] - [WithTestPatternImages(131, 127, PixelTypes.Rgba32 | PixelTypes.Bgr24)] - public void GetPixelRowSpan(TestImageProvider provider) + [WithBasicTestPatternImages(1, 1, PixelTypes.Rgba32)] + [WithBasicTestPatternImages(131, 127, PixelTypes.Rgba32)] + [WithBasicTestPatternImages(333, 555, PixelTypes.Bgr24)] + public void GetPixelRowMemory_PixelDataIsCorrect(TestImageProvider provider) where TPixel : struct, IPixel { - using (Image image = provider.GetImage()) - { - var targetBuffer = new TPixel[image.Width * image.Height]; + provider.LimitAllocatorBufferCapacity(); + + using Image image = provider.GetImage(); + for (int y = 0; y < image.Height; y++) + { // Act: - for (int y = 0; y < image.Height; y++) - { - Span rowMemory = image.GetPixelRowSpan(y); - rowMemory.CopyTo(targetBuffer.AsSpan(image.Width * y)); - } + Memory rowMemory = image.GetPixelRowMemory(y); + Span span = rowMemory.Span; // Assert: - using (Image image1 = provider.GetImage()) + for (int x = 0; x < image.Width; x++) { - // We are using a copy of the original image for assertion - image1.ComparePixelBufferTo(targetBuffer); + Assert.Equal(provider.GetExpectedBasicTestPatternPixelAt(x, y), span[x]); } } } + + [Theory] + [WithBasicTestPatternImages(16, 16, PixelTypes.Rgba32)] + public void GetPixelRowMemory_DestructiveMutate_ShouldInvalidateMemory(TestImageProvider provider) + where TPixel : struct, IPixel + { + using Image image = provider.GetImage(); + + Memory memory3 = image.GetPixelRowMemory(3); + Memory memory10 = image.GetPixelRowMemory(10); + + image.Mutate(c => c.Resize(8, 8)); + + 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 GetPixelRowSpan_ShouldReferenceSpanOfMemory(TestImageProvider provider) + where TPixel : struct, IPixel + { + provider.LimitAllocatorBufferCapacity(); + + using Image image = provider.GetImage(); + + Memory memory = image.GetPixelRowMemory(image.Height - 1); + Span span = image.GetPixelRowSpan(image.Height - 1); + + Assert.True(span == memory.Span); + } } } diff --git a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupIndex.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupIndex.cs index 88824baf2..158428f4b 100644 --- a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupIndex.cs +++ b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupIndex.cs @@ -91,25 +91,25 @@ namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers internal static class MemoryGroupIndexExtensions { - public static T GetElementAt(this MemoryGroup group, MemoryGroupIndex idx) + public static T GetElementAt(this IMemoryGroup group, MemoryGroupIndex idx) where T : struct { return group[idx.BufferIndex].Span[idx.ElementIndex]; } - public static void SetElementAt(this MemoryGroup group, MemoryGroupIndex idx, T value) + public static void SetElementAt(this IMemoryGroup group, MemoryGroupIndex idx, T value) where T : struct { group[idx.BufferIndex].Span[idx.ElementIndex] = value; } - public static MemoryGroupIndex MinIndex(this MemoryGroup group) + public static MemoryGroupIndex MinIndex(this IMemoryGroup group) where T : struct { return new MemoryGroupIndex(group.BufferLength, 0, 0); } - public static MemoryGroupIndex MaxIndex(this MemoryGroup group) + public static MemoryGroupIndex MaxIndex(this IMemoryGroup group) where T : struct { return group.Count == 0 diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BasicTestPatternProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BasicTestPatternProvider.cs index 9100e26e8..1025ed9a1 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BasicTestPatternProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BasicTestPatternProvider.cs @@ -11,16 +11,26 @@ namespace SixLabors.ImageSharp.Tests { public abstract partial class TestImageProvider : IXunitSerializable { + public virtual TPixel GetExpectedBasicTestPatternPixelAt(int x, int y) + { + throw new NotSupportedException("GetExpectedBasicTestPatternPixelAt(x,y) only works with BasicTestPattern"); + } + private class BasicTestPatternProvider : BlankProvider { + private static readonly TPixel TopLeftColor = Color.Red.ToPixel(); + private static readonly TPixel TopRightColor = Color.Green.ToPixel(); + private static readonly TPixel BottomLeftColor = Color.Blue.ToPixel(); + + // Transparent purple: + private static readonly TPixel BottomRightColor = GetBottomRightColor(); + public BasicTestPatternProvider(int width, int height) : base(width, height) { } - /// - /// This parameterless constructor is needed for xUnit deserialization - /// + // This parameterless constructor is needed for xUnit deserialization public BasicTestPatternProvider() { } @@ -31,14 +41,6 @@ namespace SixLabors.ImageSharp.Tests { var result = new Image(this.Configuration, this.Width, this.Height); - TPixel topLeftColor = Color.Red.ToPixel(); - TPixel topRightColor = Color.Green.ToPixel(); - TPixel bottomLeftColor = Color.Blue.ToPixel(); - - // Transparent purple: - TPixel bottomRightColor = default; - bottomRightColor.FromVector4(new Vector4(1f, 0f, 1f, 0.5f)); - int midY = this.Height / 2; int midX = this.Width / 2; @@ -46,20 +48,42 @@ namespace SixLabors.ImageSharp.Tests { Span row = result.GetPixelRowSpan(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 = result.GetPixelRowSpan(y); - row.Slice(0, midX).Fill(bottomLeftColor); - row.Slice(midX, this.Width - midX).Fill(bottomRightColor); + row.Slice(0, midX).Fill(BottomLeftColor); + row.Slice(midX, this.Width - midX).Fill(BottomRightColor); } return result; } + + public override TPixel GetExpectedBasicTestPatternPixelAt(int x, int y) + { + int midY = this.Height / 2; + int midX = this.Width / 2; + + if (y < midY) + { + return x < midX ? TopLeftColor : TopRightColor; + } + else + { + return x < midX ? BottomLeftColor : BottomRightColor; + } + } + + private static TPixel GetBottomRightColor() + { + TPixel bottomRightColor = default; + bottomRightColor.FromVector4(new Vector4(1f, 0f, 1f, 0.5f)); + return bottomRightColor; + } } } -} \ No newline at end of file +} From 87d1ec55e87662b2cbc216787aea21ccb0ede079 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sat, 8 Feb 2020 00:22:25 +0100 Subject: [PATCH 25/62] polish MemoryAllocator API --- ...oolMemoryAllocator.CommonFactoryMethods.cs | 8 ++- .../Allocators/ArrayPoolMemoryAllocator.cs | 41 +++++++++++++--- .../Memory/Allocators/MemoryAllocator.cs | 7 ++- .../Allocators/SimpleGcMemoryAllocator.cs | 2 +- .../InvalidMemoryOperationException.cs | 0 .../ArrayPoolMemoryAllocatorTests.cs | 49 +++++++++++++------ .../BufferExtensions.cs | 4 +- .../BufferTestSuite.cs | 3 +- .../SimpleGcMemoryAllocatorTests.cs | 3 +- 9 files changed, 86 insertions(+), 31 deletions(-) rename src/ImageSharp/Memory/{DiscontiguousBuffers => }/InvalidMemoryOperationException.cs (100%) rename tests/ImageSharp.Tests/Memory/{Alocators => Allocators}/ArrayPoolMemoryAllocatorTests.cs (84%) rename tests/ImageSharp.Tests/Memory/{Alocators => Allocators}/BufferExtensions.cs (93%) rename tests/ImageSharp.Tests/Memory/{Alocators => Allocators}/BufferTestSuite.cs (99%) rename tests/ImageSharp.Tests/Memory/{Alocators => Allocators}/SimpleGcMemoryAllocatorTests.cs (93%) diff --git a/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.CommonFactoryMethods.cs b/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.CommonFactoryMethods.cs index 1ce2525b8..5ef60c9ed 100644 --- a/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.CommonFactoryMethods.cs +++ b/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.CommonFactoryMethods.cs @@ -29,6 +29,9 @@ namespace SixLabors.ImageSharp.Memory /// 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. /// @@ -39,7 +42,8 @@ namespace SixLabors.ImageSharp.Memory DefaultMaxPooledBufferSizeInBytes, DefaultBufferSelectorThresholdInBytes, DefaultLargePoolBucketCount, - DefaultNormalPoolBucketCount); + DefaultNormalPoolBucketCount, + DefaultBufferCapacityInBytes); } /// @@ -69,4 +73,4 @@ namespace SixLabors.ImageSharp.Memory return new ArrayPoolMemoryAllocator(128 * 1024 * 1024, 32 * 1024 * 1024, 16, 32); } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.cs index 883d57851..62f23ba01 100644 --- a/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.cs +++ b/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.cs @@ -60,13 +60,41 @@ namespace SixLabors.ImageSharp.Memory /// 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) + 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; @@ -84,9 +112,9 @@ namespace SixLabors.ImageSharp.Memory public int PoolSelectorThresholdInBytes { get; } /// - /// Gets or sets the length of the largest contiguous buffer that can be handled by this allocator instance. + /// Gets the length of the largest contiguous buffer that can be handled by this allocator instance. /// - public int BufferCapacityInBytes { get; set; } = DefaultBufferCapacity; + public int BufferCapacityInBytes { get; internal set; } // Setter is internal for easy configuration in tests /// public override void ReleaseRetainedResources() @@ -103,11 +131,10 @@ namespace SixLabors.ImageSharp.Memory Guard.MustBeGreaterThanOrEqualTo(length, 0, nameof(length)); int itemSizeBytes = Unsafe.SizeOf(); int bufferSizeInBytes = length * itemSizeBytes; - if (bufferSizeInBytes < 0) + if (bufferSizeInBytes < 0 || bufferSizeInBytes > BufferCapacityInBytes) { - throw new ArgumentOutOfRangeException( - nameof(length), - $"{nameof(ArrayPoolMemoryAllocator)} can not allocate {length} elements of {typeof(T).Name}."); + throw new InvalidMemoryOperationException( + $"Requested allocation {length} elements of {typeof(T).Name} is over the capacity of the MemoryAllocator."); } ArrayPool pool = this.GetArrayPool(bufferSizeInBytes); diff --git a/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs index c6e92f23f..d02d50d9d 100644 --- a/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs +++ b/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System; using System.Buffers; namespace SixLabors.ImageSharp.Memory @@ -10,7 +11,7 @@ namespace SixLabors.ImageSharp.Memory /// public abstract class MemoryAllocator { - internal const int DefaultBufferCapacity = int.MaxValue / 2; + /// /// Gets the length of the largest contiguous buffer that can be handled by this allocator instance in bytes. @@ -25,6 +26,8 @@ namespace SixLabors.ImageSharp.Memory /// Size of the buffer to allocate. /// The allocation options. /// A buffer of values of type . + /// When length is zero or negative. + /// When length is over the capacity of the allocator. public abstract IMemoryOwner Allocate(int length, AllocationOptions options = AllocationOptions.None) where T : struct; @@ -34,6 +37,8 @@ namespace SixLabors.ImageSharp.Memory /// 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); /// diff --git a/src/ImageSharp/Memory/Allocators/SimpleGcMemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/SimpleGcMemoryAllocator.cs index b417df351..4c62e4ded 100644 --- a/src/ImageSharp/Memory/Allocators/SimpleGcMemoryAllocator.cs +++ b/src/ImageSharp/Memory/Allocators/SimpleGcMemoryAllocator.cs @@ -12,7 +12,7 @@ namespace SixLabors.ImageSharp.Memory public sealed class SimpleGcMemoryAllocator : MemoryAllocator { /// - protected internal override int GetBufferCapacityInBytes() => DefaultBufferCapacity; + protected internal override int GetBufferCapacityInBytes() => int.MaxValue; /// public override IMemoryOwner Allocate(int length, AllocationOptions options = AllocationOptions.None) diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/InvalidMemoryOperationException.cs b/src/ImageSharp/Memory/InvalidMemoryOperationException.cs similarity index 100% rename from src/ImageSharp/Memory/DiscontiguousBuffers/InvalidMemoryOperationException.cs rename to src/ImageSharp/Memory/InvalidMemoryOperationException.cs diff --git a/tests/ImageSharp.Tests/Memory/Alocators/ArrayPoolMemoryAllocatorTests.cs b/tests/ImageSharp.Tests/Memory/Allocators/ArrayPoolMemoryAllocatorTests.cs similarity index 84% rename from tests/ImageSharp.Tests/Memory/Alocators/ArrayPoolMemoryAllocatorTests.cs rename to tests/ImageSharp.Tests/Memory/Allocators/ArrayPoolMemoryAllocatorTests.cs index dd497e57e..1e079fcf5 100644 --- a/tests/ImageSharp.Tests/Memory/Alocators/ArrayPoolMemoryAllocatorTests.cs +++ b/tests/ImageSharp.Tests/Memory/Allocators/ArrayPoolMemoryAllocatorTests.cs @@ -1,18 +1,15 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -// ReSharper disable InconsistentNaming using System; using System.Buffers; -using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Microsoft.DotNet.RemoteExecutor; -using Microsoft.Win32; -using SixLabors.ImageSharp.Tests; +using SixLabors.ImageSharp.Memory; using Xunit; -namespace SixLabors.ImageSharp.Memory.Tests +namespace SixLabors.ImageSharp.Tests.Memory.Allocators { public class ArrayPoolMemoryAllocatorTests { @@ -116,13 +113,13 @@ namespace SixLabors.ImageSharp.Memory.Tests MemoryAllocator memoryAllocator = this.LocalFixture.MemoryAllocator; using (IMemoryOwner firstAlloc = memoryAllocator.Allocate(42)) { - firstAlloc.GetSpan().Fill(666); + BufferExtensions.GetSpan(firstAlloc).Fill(666); } using (IMemoryOwner secondAlloc = memoryAllocator.Allocate(42, options)) { int expected = options == AllocationOptions.Clean ? 0 : 666; - Assert.Equal(expected, secondAlloc.GetSpan()[0]); + Assert.Equal(expected, BufferExtensions.GetSpan(secondAlloc)[0]); } } @@ -133,7 +130,7 @@ namespace SixLabors.ImageSharp.Memory.Tests { MemoryAllocator memoryAllocator = this.LocalFixture.MemoryAllocator; IMemoryOwner buffer = memoryAllocator.Allocate(32); - ref int ptrToPrev0 = ref MemoryMarshal.GetReference(buffer.GetSpan()); + ref int ptrToPrev0 = ref MemoryMarshal.GetReference(BufferExtensions.GetSpan(buffer)); if (!keepBufferAlive) { @@ -144,7 +141,7 @@ namespace SixLabors.ImageSharp.Memory.Tests buffer = memoryAllocator.Allocate(32); - Assert.False(Unsafe.AreSame(ref ptrToPrev0, ref buffer.GetReference())); + Assert.False(Unsafe.AreSame(ref ptrToPrev0, ref BufferExtensions.GetReference(buffer))); } [Fact] @@ -164,12 +161,12 @@ namespace SixLabors.ImageSharp.Memory.Tests const int ArrayLengthThreshold = PoolSelectorThresholdInBytes / sizeof(int); IMemoryOwner small = StaticFixture.MemoryAllocator.Allocate(ArrayLengthThreshold - 1); - ref int ptr2Small = ref small.GetReference(); + ref int ptr2Small = ref BufferExtensions.GetReference(small); small.Dispose(); IMemoryOwner large = StaticFixture.MemoryAllocator.Allocate(ArrayLengthThreshold + 1); - Assert.False(Unsafe.AreSame(ref ptr2Small, ref large.GetReference())); + Assert.False(Unsafe.AreSame(ref ptr2Small, ref BufferExtensions.GetReference(large))); } RemoteExecutor.Invoke(RunTest).Dispose(); @@ -216,14 +213,34 @@ namespace SixLabors.ImageSharp.Memory.Tests [Theory] [InlineData(-1)] - [InlineData((int.MaxValue / SizeOfLargeStruct) + 1)] - public void AllocateIncorrectAmount_ThrowsCorrect_ArgumentOutOfRangeException(int length) + [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); + } + + [Theory] + [InlineData(101)] + [InlineData((int.MaxValue / SizeOfLargeStruct) - 1)] + [InlineData(int.MaxValue / SizeOfLargeStruct)] + [InlineData((int.MaxValue / SizeOfLargeStruct) + 1)] + [InlineData((int.MaxValue / SizeOfLargeStruct) + 137)] + public void Allocate_OverCapacity_Throws_InvalidMemoryOperationException(int length) + { + this.LocalFixture.MemoryAllocator.BufferCapacityInBytes = 100 * SizeOfLargeStruct; + Assert.Throws(() => + this.LocalFixture.MemoryAllocator.Allocate(length)); + } + [Theory] [InlineData(-1)] public void AllocateManagedByteBuffer_IncorrectAmount_ThrowsCorrect_ArgumentOutOfRangeException(int length) @@ -235,7 +252,7 @@ namespace SixLabors.ImageSharp.Memory.Tests private class MemoryAllocatorFixture { - public MemoryAllocator MemoryAllocator { get; set; } = + public ArrayPoolMemoryAllocator MemoryAllocator { get; set; } = new ArrayPoolMemoryAllocator(MaxPooledBufferSizeInBytes, PoolSelectorThresholdInBytes); /// @@ -245,11 +262,11 @@ namespace SixLabors.ImageSharp.Memory.Tests where T : struct { IMemoryOwner buffer = this.MemoryAllocator.Allocate(length); - ref T ptrToPrevPosition0 = ref buffer.GetReference(); + ref T ptrToPrevPosition0 = ref BufferExtensions.GetReference(buffer); buffer.Dispose(); buffer = this.MemoryAllocator.Allocate(length); - bool sameBuffers = Unsafe.AreSame(ref ptrToPrevPosition0, ref buffer.GetReference()); + bool sameBuffers = Unsafe.AreSame(ref ptrToPrevPosition0, ref BufferExtensions.GetReference(buffer)); buffer.Dispose(); return sameBuffers; diff --git a/tests/ImageSharp.Tests/Memory/Alocators/BufferExtensions.cs b/tests/ImageSharp.Tests/Memory/Allocators/BufferExtensions.cs similarity index 93% rename from tests/ImageSharp.Tests/Memory/Alocators/BufferExtensions.cs rename to tests/ImageSharp.Tests/Memory/Allocators/BufferExtensions.cs index 8073d069d..9f8543fff 100644 --- a/tests/ImageSharp.Tests/Memory/Alocators/BufferExtensions.cs +++ b/tests/ImageSharp.Tests/Memory/Allocators/BufferExtensions.cs @@ -6,7 +6,7 @@ using System.Buffers; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.Memory.Tests +namespace SixLabors.ImageSharp.Tests.Memory.Allocators { internal static class BufferExtensions { @@ -22,4 +22,4 @@ namespace SixLabors.ImageSharp.Memory.Tests where T : struct => ref MemoryMarshal.GetReference(buffer.GetSpan()); } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Memory/Alocators/BufferTestSuite.cs b/tests/ImageSharp.Tests/Memory/Allocators/BufferTestSuite.cs similarity index 99% rename from tests/ImageSharp.Tests/Memory/Alocators/BufferTestSuite.cs rename to tests/ImageSharp.Tests/Memory/Allocators/BufferTestSuite.cs index 4590bbe97..6465e0b81 100644 --- a/tests/ImageSharp.Tests/Memory/Alocators/BufferTestSuite.cs +++ b/tests/ImageSharp.Tests/Memory/Allocators/BufferTestSuite.cs @@ -5,10 +5,11 @@ using System; using System.Buffers; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Memory; using Xunit; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Memory.Tests +namespace SixLabors.ImageSharp.Tests.Memory.Allocators { /// /// Inherit this class to test an implementation (provided by ). diff --git a/tests/ImageSharp.Tests/Memory/Alocators/SimpleGcMemoryAllocatorTests.cs b/tests/ImageSharp.Tests/Memory/Allocators/SimpleGcMemoryAllocatorTests.cs similarity index 93% rename from tests/ImageSharp.Tests/Memory/Alocators/SimpleGcMemoryAllocatorTests.cs rename to tests/ImageSharp.Tests/Memory/Allocators/SimpleGcMemoryAllocatorTests.cs index 8e3b82be5..9e14bd1db 100644 --- a/tests/ImageSharp.Tests/Memory/Alocators/SimpleGcMemoryAllocatorTests.cs +++ b/tests/ImageSharp.Tests/Memory/Allocators/SimpleGcMemoryAllocatorTests.cs @@ -3,9 +3,10 @@ using System; using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Memory; using Xunit; -namespace SixLabors.ImageSharp.Memory.Tests +namespace SixLabors.ImageSharp.Tests.Memory.Allocators { public class SimpleGcMemoryAllocatorTests { From 2a4c8492f94cfa4968ea6402fe550c05b51d43c9 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sat, 8 Feb 2020 02:51:16 +0100 Subject: [PATCH 26/62] Clean up public API --- .../Advanced/AdvancedImageExtensions.cs | 44 ++++++++++++++++--- .../Common/Helpers/Buffer2DUtils.cs | 4 +- .../Common/Helpers/DenseMatrixUtils.cs | 4 +- src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs | 20 ++++----- src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs | 6 +-- .../ColorConverters/JpegColorConverter.cs | 8 ++-- .../Components/Decoder/HuffmanScanDecoder.cs | 10 ++--- .../Decoder/JpegComponentPostProcessor.cs | 2 +- .../Formats/Jpeg/Components/RowOctet.cs | 16 +++---- src/ImageSharp/Formats/Tga/TgaDecoderCore.cs | 14 +++--- src/ImageSharp/Formats/Tga/TgaEncoderCore.cs | 8 ++-- .../Allocators/ArrayPoolMemoryAllocator.cs | 4 +- .../Memory/Allocators/MemoryAllocator.cs | 2 - src/ImageSharp/Memory/Buffer2DExtensions.cs | 21 ++++++++- src/ImageSharp/Memory/Buffer2D{T}.cs | 33 +++++++++++++- .../MemoryGroupExtensions.cs | 20 ++++----- .../Convolution/BokehBlurProcessor{TPixel}.cs | 10 ++--- .../Convolution2DProcessor{TPixel}.cs | 2 +- .../Convolution2PassProcessor{TPixel}.cs | 2 +- .../ConvolutionProcessor{TPixel}.cs | 2 +- .../EdgeDetectorCompassProcessor{TPixel}.cs | 4 +- .../Effects/OilPaintingProcessor{TPixel}.cs | 2 +- ...eHistogramEqualizationProcessor{TPixel}.cs | 4 +- .../Transforms/Resize/ResizeKernelMap.cs | 2 +- .../Transforms/Resize/ResizeWorker.cs | 4 +- .../Transforms/TransformKernelMap.cs | 4 +- .../PixelBlenders/PorterDuffBulkVsPixel.cs | 4 +- .../Jpg/Utils/LibJpegTools.ComponentData.cs | 2 +- .../ImageSharp.Tests/Memory/Buffer2DTests.cs | 6 +-- .../Memory/BufferAreaTests.cs | 4 +- 30 files changed, 171 insertions(+), 97 deletions(-) diff --git a/src/ImageSharp/Advanced/AdvancedImageExtensions.cs b/src/ImageSharp/Advanced/AdvancedImageExtensions.cs index fc6ba10b0..fd9f98ac9 100644 --- a/src/ImageSharp/Advanced/AdvancedImageExtensions.cs +++ b/src/ImageSharp/Advanced/AdvancedImageExtensions.cs @@ -61,7 +61,7 @@ namespace SixLabors.ImageSharp.Advanced /// public static IMemoryGroup GetPixelMemoryGroup(this ImageFrame source) where TPixel : struct, IPixel - => source.PixelBuffer.MemoryGroup.View; + => source?.PixelBuffer.MemoryGroup.View ?? throw new ArgumentNullException(nameof(source)); /// /// Gets the representation of the pixels as a containing the backing pixel data of the image @@ -76,7 +76,7 @@ namespace SixLabors.ImageSharp.Advanced /// public static IMemoryGroup GetPixelMemoryGroup(this Image source) where TPixel : struct, IPixel - => source.Frames.RootFrame.GetPixelMemoryGroup(); + => source?.Frames.RootFrame.GetPixelMemoryGroup() ?? throw new ArgumentNullException(nameof(source)); /// /// Gets the representation of the pixels as a in the source image's pixel format @@ -91,6 +91,8 @@ namespace SixLabors.ImageSharp.Advanced public static Span GetPixelSpan(this ImageFrame source) where TPixel : struct, IPixel { + Guard.NotNull(source, nameof(source)); + IMemoryGroup mg = source.GetPixelMemoryGroup(); if (mg.Count > 1) { @@ -112,7 +114,11 @@ namespace SixLabors.ImageSharp.Advanced @"GetPixelSpan might fail, because the backing buffer could be discontiguous for large images. Use GetPixelMemoryGroup or GetPixelRowSpan instead!")] public static Span GetPixelSpan(this Image source) where TPixel : struct, IPixel - => source.Frames.RootFrame.GetPixelSpan(); + { + Guard.NotNull(source, nameof(source)); + + return source.Frames.RootFrame.GetPixelSpan(); + } /// /// Gets the representation of the pixels as a of contiguous memory @@ -124,7 +130,13 @@ namespace SixLabors.ImageSharp.Advanced /// The public static Span GetPixelRowSpan(this ImageFrame source, int rowIndex) where TPixel : struct, IPixel - => source.PixelBuffer.GetRowSpan(rowIndex); + { + Guard.NotNull(source, nameof(source)); + Guard.MustBeGreaterThanOrEqualTo(rowIndex, 0, nameof(rowIndex)); + Guard.MustBeLessThan(rowIndex, source.Height, nameof(rowIndex)); + + return source.PixelBuffer.GetRowSpanUnchecked(rowIndex); + } /// /// Gets the representation of the pixels as of of contiguous memory @@ -136,7 +148,13 @@ namespace SixLabors.ImageSharp.Advanced /// The public static Span GetPixelRowSpan(this Image source, int rowIndex) where TPixel : struct, IPixel - => source.Frames.RootFrame.GetPixelRowSpan(rowIndex); + { + Guard.NotNull(source, nameof(source)); + Guard.MustBeGreaterThanOrEqualTo(rowIndex, 0, nameof(rowIndex)); + Guard.MustBeLessThan(rowIndex, source.Height, nameof(rowIndex)); + + return source.Frames.RootFrame.PixelBuffer.GetRowSpanUnchecked(rowIndex); + } /// /// Gets the representation of the pixels as a of contiguous memory @@ -148,7 +166,13 @@ namespace SixLabors.ImageSharp.Advanced /// The public static Memory GetPixelRowMemory(this ImageFrame source, int rowIndex) where TPixel : struct, IPixel - => source.PixelBuffer.GetRowMemorySafe(rowIndex); + { + Guard.NotNull(source, nameof(source)); + Guard.MustBeGreaterThanOrEqualTo(rowIndex, 0, nameof(rowIndex)); + Guard.MustBeLessThan(rowIndex, source.Height, nameof(rowIndex)); + + return source.PixelBuffer.GetRowMemorySafe(rowIndex); + } /// /// Gets the representation of the pixels as of of contiguous memory @@ -160,7 +184,13 @@ namespace SixLabors.ImageSharp.Advanced /// The public static Memory GetPixelRowMemory(this Image source, int rowIndex) where TPixel : struct, IPixel - => source.Frames.RootFrame.GetPixelRowMemory(rowIndex); + { + Guard.NotNull(source, nameof(source)); + Guard.MustBeGreaterThanOrEqualTo(rowIndex, 0, nameof(rowIndex)); + Guard.MustBeLessThan(rowIndex, source.Height, nameof(rowIndex)); + + return source.Frames.RootFrame.PixelBuffer.GetRowMemorySafe(rowIndex); + } /// /// Gets the assigned to 'source'. diff --git a/src/ImageSharp/Common/Helpers/Buffer2DUtils.cs b/src/ImageSharp/Common/Helpers/Buffer2DUtils.cs index f82774601..677520ddd 100644 --- a/src/ImageSharp/Common/Helpers/Buffer2DUtils.cs +++ b/src/ImageSharp/Common/Helpers/Buffer2DUtils.cs @@ -52,7 +52,7 @@ namespace SixLabors.ImageSharp { int offsetY = (row + i - radiusY).Clamp(minRow, maxRow); int offsetX = sourceOffsetColumnBase.Clamp(minColumn, maxColumn); - Span sourceRowSpan = sourcePixels.GetRowSpan(offsetY); + Span sourceRowSpan = sourcePixels.GetRowSpanUnchecked(offsetY); var currentColor = sourceRowSpan[offsetX].ToVector4(); vector.Sum(Unsafe.Add(ref baseRef, i) * currentColor); @@ -94,7 +94,7 @@ namespace SixLabors.ImageSharp int sourceOffsetColumnBase = column + minColumn; int offsetY = row.Clamp(minRow, maxRow); - ref ComplexVector4 sourceRef = ref MemoryMarshal.GetReference(sourceValues.GetRowSpan(offsetY)); + ref ComplexVector4 sourceRef = ref MemoryMarshal.GetReference(sourceValues.GetRowSpanUnchecked(offsetY)); ref Complex64 baseRef = ref MemoryMarshal.GetReference(kernel); for (int x = 0; x < kernelLength; x++) diff --git a/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs b/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs index ff6e3a4ec..baab397f8 100644 --- a/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs +++ b/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs @@ -134,7 +134,7 @@ namespace SixLabors.ImageSharp for (int y = 0; y < matrixHeight; y++) { int offsetY = (row + y - radiusY).Clamp(minRow, maxRow); - Span sourceRowSpan = sourcePixels.GetRowSpan(offsetY); + Span sourceRowSpan = sourcePixels.GetRowSpanUnchecked(offsetY); for (int x = 0; x < matrixWidth; x++) { @@ -264,7 +264,7 @@ namespace SixLabors.ImageSharp for (int y = 0; y < matrixHeight; y++) { int offsetY = (row + y - radiusY).Clamp(minRow, maxRow); - Span sourceRowSpan = sourcePixels.GetRowSpan(offsetY); + Span sourceRowSpan = sourcePixels.GetRowSpanUnchecked(offsetY); for (int x = 0; x < matrixWidth; x++) { diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs index 3e1637e70..c46504cce 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs @@ -311,8 +311,8 @@ namespace SixLabors.ImageSharp.Formats.Bmp for (int y = 0; y < height; y++) { int newY = Invert(y, height, inverted); - Span bufferRow = buffer.GetRowSpan(y); - Span pixelRow = pixels.GetRowSpan(newY); + Span bufferRow = buffer.GetRowSpanUnchecked(y); + Span pixelRow = pixels.GetRowSpanUnchecked(newY); bool rowHasUndefinedPixels = rowsWithUndefinedPixelsSpan[y]; if (rowHasUndefinedPixels) @@ -381,7 +381,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.GetRowSpanUnchecked(newY); bool rowHasUndefinedPixels = rowsWithUndefinedPixelsSpan[y]; if (rowHasUndefinedPixels) { @@ -830,7 +830,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp int newY = Invert(y, height, inverted); this.stream.Read(row.Array, 0, row.Length()); int offset = 0; - Span pixelRow = pixels.GetRowSpan(newY); + Span pixelRow = pixels.GetRowSpanUnchecked(newY); for (int x = 0; x < arrayWidth; x++) { @@ -882,7 +882,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp { this.stream.Read(buffer.Array, 0, stride); int newY = Invert(y, height, inverted); - Span pixelRow = pixels.GetRowSpan(newY); + Span pixelRow = pixels.GetRowSpanUnchecked(newY); int offset = 0; for (int x = 0; x < width; x++) @@ -938,7 +938,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp { this.stream.Read(row); int newY = Invert(y, height, inverted); - Span pixelSpan = pixels.GetRowSpan(newY); + Span pixelSpan = pixels.GetRowSpanUnchecked(newY); PixelOperations.Instance.FromBgr24Bytes( this.configuration, row.GetSpan(), @@ -967,7 +967,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp { this.stream.Read(row); int newY = Invert(y, height, inverted); - Span pixelSpan = pixels.GetRowSpan(newY); + Span pixelSpan = pixels.GetRowSpanUnchecked(newY); PixelOperations.Instance.FromBgra32Bytes( this.configuration, row.GetSpan(), @@ -1039,7 +1039,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp this.stream.Read(row); int newY = Invert(y, height, inverted); - Span pixelSpan = pixels.GetRowSpan(newY); + Span pixelSpan = pixels.GetRowSpanUnchecked(newY); PixelOperations.Instance.FromBgra32Bytes( this.configuration, @@ -1062,7 +1062,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp width); int newY = Invert(y, height, inverted); - Span pixelSpan = pixels.GetRowSpan(newY); + Span pixelSpan = pixels.GetRowSpanUnchecked(newY); for (int x = 0; x < width; x++) { @@ -1117,7 +1117,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp { this.stream.Read(buffer.Array, 0, stride); int newY = Invert(y, height, inverted); - Span pixelRow = pixels.GetRowSpan(newY); + Span pixelRow = pixels.GetRowSpanUnchecked(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 1c7c606ca..12056fb0c 100644 --- a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs @@ -240,7 +240,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp { for (int y = pixels.Height - 1; y >= 0; y--) { - Span pixelSpan = pixels.GetRowSpan(y); + Span pixelSpan = pixels.GetRowSpanUnchecked(y); PixelOperations.Instance.ToBgra32Bytes( this.configuration, pixelSpan, @@ -264,7 +264,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp { for (int y = pixels.Height - 1; y >= 0; y--) { - Span pixelSpan = pixels.GetRowSpan(y); + Span pixelSpan = pixels.GetRowSpanUnchecked(y); PixelOperations.Instance.ToBgr24Bytes( this.configuration, pixelSpan, @@ -288,7 +288,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp { for (int y = pixels.Height - 1; y >= 0; y--) { - Span pixelSpan = pixels.GetRowSpan(y); + Span pixelSpan = pixels.GetRowSpanUnchecked(y); PixelOperations.Instance.ToBgra5551Bytes( this.configuration, diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.cs index 61e359869..87f5233e9 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.cs @@ -136,20 +136,20 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { this.ComponentCount = componentBuffers.Count; - this.Component0 = componentBuffers[0].GetRowSpan(row); + this.Component0 = componentBuffers[0].GetRowSpanUnchecked(row); this.Component1 = Span.Empty; this.Component2 = Span.Empty; this.Component3 = Span.Empty; if (this.ComponentCount > 1) { - this.Component1 = componentBuffers[1].GetRowSpan(row); + this.Component1 = componentBuffers[1].GetRowSpanUnchecked(row); if (this.ComponentCount > 2) { - this.Component2 = componentBuffers[2].GetRowSpan(row); + this.Component2 = componentBuffers[2].GetRowSpanUnchecked(row); if (this.ComponentCount > 3) { - this.Component3 = componentBuffers[3].GetRowSpan(row); + this.Component3 = componentBuffers[3].GetRowSpanUnchecked(row); } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs index fbb2b5272..294d1da19 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs @@ -167,7 +167,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.GetRowSpanUnchecked(blockRow); ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan); for (int x = 0; x < h; x++) @@ -211,7 +211,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder for (int j = 0; j < h; j++) { - Span blockSpan = component.SpectralBlocks.GetRowSpan(j); + Span blockSpan = component.SpectralBlocks.GetRowSpanUnchecked(j); ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan); for (int i = 0; i < w; i++) @@ -334,7 +334,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.GetRowSpanUnchecked(blockRow); ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan); for (int x = 0; x < h; x++) @@ -377,7 +377,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder for (int j = 0; j < h; j++) { - Span blockSpan = component.SpectralBlocks.GetRowSpan(j); + Span blockSpan = component.SpectralBlocks.GetRowSpanUnchecked(j); ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan); for (int i = 0; i < w; i++) @@ -403,7 +403,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder for (int j = 0; j < h; j++) { - Span blockSpan = component.SpectralBlocks.GetRowSpan(j); + Span blockSpan = component.SpectralBlocks.GetRowSpanUnchecked(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 39c8be312..5a52c3ac4 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs @@ -89,7 +89,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder int yBuffer = y * this.blockAreaSize.Height; - Span blockRow = this.Component.SpectralBlocks.GetRowSpan(yBlock); + Span blockRow = this.Component.SpectralBlocks.GetRowSpanUnchecked(yBlock); ref Block8x8 blockRowBase = ref MemoryMarshal.GetReference(blockRow); diff --git a/src/ImageSharp/Formats/Jpeg/Components/RowOctet.cs b/src/ImageSharp/Formats/Jpeg/Components/RowOctet.cs index 57a134703..54976110b 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/RowOctet.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/RowOctet.cs @@ -27,14 +27,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components { int y = startY; int height = buffer.Height; - this.row0 = y < height ? buffer.GetRowSpan(y++) : default; - this.row1 = y < height ? buffer.GetRowSpan(y++) : default; - this.row2 = y < height ? buffer.GetRowSpan(y++) : default; - this.row3 = y < height ? buffer.GetRowSpan(y++) : default; - this.row4 = y < height ? buffer.GetRowSpan(y++) : default; - this.row5 = y < height ? buffer.GetRowSpan(y++) : default; - this.row6 = y < height ? buffer.GetRowSpan(y++) : default; - this.row7 = y < height ? buffer.GetRowSpan(y) : default; + this.row0 = y < height ? buffer.GetRowSpanUnchecked(y++) : default; + this.row1 = y < height ? buffer.GetRowSpanUnchecked(y++) : default; + this.row2 = y < height ? buffer.GetRowSpanUnchecked(y++) : default; + this.row3 = y < height ? buffer.GetRowSpanUnchecked(y++) : default; + this.row4 = y < height ? buffer.GetRowSpanUnchecked(y++) : default; + this.row5 = y < height ? buffer.GetRowSpanUnchecked(y++) : default; + this.row6 = y < height ? buffer.GetRowSpanUnchecked(y++) : default; + this.row7 = y < height ? buffer.GetRowSpanUnchecked(y) : default; } public Span this[int y] diff --git a/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs b/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs index 91cc93e19..5846e88dc 100644 --- a/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs +++ b/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs @@ -228,7 +228,7 @@ namespace SixLabors.ImageSharp.Formats.Tga { this.currentStream.Read(row); int newY = Invert(y, height, inverted); - Span pixelRow = pixels.GetRowSpan(newY); + Span pixelRow = pixels.GetRowSpanUnchecked(newY); switch (colorMapPixelSizeInBytes) { case 2: @@ -292,7 +292,7 @@ namespace SixLabors.ImageSharp.Formats.Tga for (int y = 0; y < height; y++) { int newY = Invert(y, height, inverted); - Span pixelRow = pixels.GetRowSpan(newY); + Span pixelRow = pixels.GetRowSpanUnchecked(newY); int rowStartIdx = y * width * bytesPerPixel; for (int x = 0; x < width; x++) { @@ -339,7 +339,7 @@ namespace SixLabors.ImageSharp.Formats.Tga { this.currentStream.Read(row); int newY = Invert(y, height, inverted); - Span pixelSpan = pixels.GetRowSpan(newY); + Span pixelSpan = pixels.GetRowSpanUnchecked(newY); PixelOperations.Instance.FromL8Bytes( this.configuration, row.GetSpan(), @@ -374,7 +374,7 @@ namespace SixLabors.ImageSharp.Formats.Tga } int newY = Invert(y, height, inverted); - Span pixelSpan = pixels.GetRowSpan(newY); + Span pixelSpan = pixels.GetRowSpanUnchecked(newY); PixelOperations.Instance.FromBgra5551Bytes( this.configuration, rowSpan, @@ -401,7 +401,7 @@ namespace SixLabors.ImageSharp.Formats.Tga { this.currentStream.Read(row); int newY = Invert(y, height, inverted); - Span pixelSpan = pixels.GetRowSpan(newY); + Span pixelSpan = pixels.GetRowSpanUnchecked(newY); PixelOperations.Instance.FromBgr24Bytes( this.configuration, row.GetSpan(), @@ -428,7 +428,7 @@ namespace SixLabors.ImageSharp.Formats.Tga { this.currentStream.Read(row); int newY = Invert(y, height, inverted); - Span pixelSpan = pixels.GetRowSpan(newY); + Span pixelSpan = pixels.GetRowSpanUnchecked(newY); PixelOperations.Instance.FromBgra32Bytes( this.configuration, row.GetSpan(), @@ -458,7 +458,7 @@ namespace SixLabors.ImageSharp.Formats.Tga for (int y = 0; y < height; y++) { int newY = Invert(y, height, inverted); - Span pixelRow = pixels.GetRowSpan(newY); + Span pixelRow = pixels.GetRowSpanUnchecked(newY); int rowStartIdx = y * width * bytesPerPixel; for (int x = 0; x < width; x++) { diff --git a/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs b/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs index 1306061c5..f3451a8e2 100644 --- a/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs +++ b/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs @@ -244,7 +244,7 @@ namespace SixLabors.ImageSharp.Formats.Tga { for (int y = pixels.Height - 1; y >= 0; y--) { - Span pixelSpan = pixels.GetRowSpan(y); + Span pixelSpan = pixels.GetRowSpanUnchecked(y); PixelOperations.Instance.ToL8Bytes( this.configuration, pixelSpan, @@ -268,7 +268,7 @@ namespace SixLabors.ImageSharp.Formats.Tga { for (int y = pixels.Height - 1; y >= 0; y--) { - Span pixelSpan = pixels.GetRowSpan(y); + Span pixelSpan = pixels.GetRowSpanUnchecked(y); PixelOperations.Instance.ToBgra5551Bytes( this.configuration, pixelSpan, @@ -292,7 +292,7 @@ namespace SixLabors.ImageSharp.Formats.Tga { for (int y = pixels.Height - 1; y >= 0; y--) { - Span pixelSpan = pixels.GetRowSpan(y); + Span pixelSpan = pixels.GetRowSpanUnchecked(y); PixelOperations.Instance.ToBgr24Bytes( this.configuration, pixelSpan, @@ -316,7 +316,7 @@ namespace SixLabors.ImageSharp.Formats.Tga { for (int y = pixels.Height - 1; y >= 0; y--) { - Span pixelSpan = pixels.GetRowSpan(y); + Span pixelSpan = pixels.GetRowSpanUnchecked(y); PixelOperations.Instance.ToBgra32Bytes( this.configuration, pixelSpan, diff --git a/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.cs index 62f23ba01..8043c1888 100644 --- a/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.cs +++ b/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.cs @@ -131,10 +131,10 @@ namespace SixLabors.ImageSharp.Memory Guard.MustBeGreaterThanOrEqualTo(length, 0, nameof(length)); int itemSizeBytes = Unsafe.SizeOf(); int bufferSizeInBytes = length * itemSizeBytes; - if (bufferSizeInBytes < 0 || bufferSizeInBytes > BufferCapacityInBytes) + if (bufferSizeInBytes < 0 || bufferSizeInBytes > this.BufferCapacityInBytes) { throw new InvalidMemoryOperationException( - $"Requested allocation {length} elements of {typeof(T).Name} is over the capacity of the MemoryAllocator."); + $"Requested allocation: {length} elements of {typeof(T).Name} is over the capacity of the MemoryAllocator."); } ArrayPool pool = this.GetArrayPool(bufferSizeInBytes); diff --git a/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs index d02d50d9d..a4e1de197 100644 --- a/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs +++ b/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs @@ -11,8 +11,6 @@ namespace SixLabors.ImageSharp.Memory /// public abstract class MemoryAllocator { - - /// /// Gets the length of the largest contiguous buffer that can be handled by this allocator instance in bytes. /// diff --git a/src/ImageSharp/Memory/Buffer2DExtensions.cs b/src/ImageSharp/Memory/Buffer2DExtensions.cs index 829a2767a..810d55a77 100644 --- a/src/ImageSharp/Memory/Buffer2DExtensions.cs +++ b/src/ImageSharp/Memory/Buffer2DExtensions.cs @@ -14,6 +14,19 @@ namespace SixLabors.ImageSharp.Memory /// public static class Buffer2DExtensions { + /// + /// Gets the backing . + /// + /// The buffer. + /// The element type. + /// The MemoryGroup. + public static IMemoryGroup GetMemoryGroup(this Buffer2D buffer) + where T : struct + { + Guard.NotNull(buffer, nameof(buffer)); + return buffer.MemoryGroup.View; + } + /// /// Gets a to the backing data of /// if the backing group consists of one single contiguous memory buffer. @@ -25,7 +38,9 @@ namespace SixLabors.ImageSharp.Memory /// /// Thrown when the backing group is discontiguous. /// - public static Span GetSingleSpan(this Buffer2D buffer) + // TODO: Review all usages, should be only used with buffers which do not scale fully with image size! + [Obsolete("TODO: Review all usages!")] + internal static Span GetSingleSpan(this Buffer2D buffer) where T : struct { Guard.NotNull(buffer, nameof(buffer)); @@ -48,7 +63,9 @@ namespace SixLabors.ImageSharp.Memory /// /// Thrown when the backing group is discontiguous. /// - public static Memory GetSingleMemory(this Buffer2D buffer) + // TODO: Review all usages, should be only used with buffers which do not scale fully with image size! + [Obsolete("TODO: Review all usages!")] + internal static Memory GetSingleMemory(this Buffer2D buffer) where T : struct { Guard.NotNull(buffer, nameof(buffer)); diff --git a/src/ImageSharp/Memory/Buffer2D{T}.cs b/src/ImageSharp/Memory/Buffer2D{T}.cs index bb00b48cd..29e22767f 100644 --- a/src/ImageSharp/Memory/Buffer2D{T}.cs +++ b/src/ImageSharp/Memory/Buffer2D{T}.cs @@ -50,6 +50,11 @@ namespace SixLabors.ImageSharp.Memory /// /// Gets the backing . /// + /// + /// This property has been kept internal intentionally. + /// It's public counterpart is , + /// which only exposes the view of the MemoryGroup. + /// internal MemoryGroup MemoryGroup { get; } /// @@ -66,7 +71,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.GetRowSpanUnchecked(y)[x]; } } @@ -78,6 +83,9 @@ namespace SixLabors.ImageSharp.Memory [MethodImpl(MethodImplOptions.AggressiveInlining)] public Span GetRowSpan(int y) { + Guard.MustBeGreaterThanOrEqualTo(y, 0, nameof(y)); + Guard.MustBeLessThan(y, this.Height, nameof(y)); + return this.cachedMemory.Length > 0 ? this.cachedMemory.Span.Slice(y * this.Width, this.Width) : this.GetRowMemorySlow(y).Span; @@ -92,6 +100,20 @@ namespace SixLabors.ImageSharp.Memory this.cachedMemory = default; } + /// + /// Same as , but does not validate index in Release mode. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal Span GetRowSpanUnchecked(int y) + { + 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; + } + /// /// 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 @@ -102,6 +124,8 @@ namespace SixLabors.ImageSharp.Memory [MethodImpl(MethodImplOptions.AggressiveInlining)] internal Memory GetRowMemoryFast(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); @@ -112,7 +136,12 @@ namespace SixLabors.ImageSharp.Memory /// /// The y (row) coordinate. /// The . - internal Memory GetRowMemorySafe(int y) => this.MemoryGroup.View.GetBoundedSlice(y * this.Width, this.Width); + internal Memory GetRowMemorySafe(int y) + { + DebugGuard.MustBeGreaterThanOrEqualTo(y, 0, nameof(y)); + DebugGuard.MustBeLessThan(y, this.Height, nameof(y)); + return this.MemoryGroup.View.GetBoundedSlice(y * this.Width, this.Width); + } /// /// Swaps the contents of 'destination' with 'source' if the buffers are owned (1), diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupExtensions.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupExtensions.cs index 1b4c297f3..7d6afbc7b 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupExtensions.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupExtensions.cs @@ -7,7 +7,7 @@ namespace SixLabors.ImageSharp.Memory { internal static class MemoryGroupExtensions { - public static void Fill(this IMemoryGroup group, T value) + internal static void Fill(this IMemoryGroup group, T value) where T : struct { foreach (Memory memory in group) @@ -16,7 +16,7 @@ namespace SixLabors.ImageSharp.Memory } } - public static void Clear(this IMemoryGroup group) + internal static void Clear(this IMemoryGroup group) where T : struct { foreach (Memory memory in group) @@ -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. /// - public static Memory GetBoundedSlice(this IMemoryGroup group, long start, int length) + internal static Memory GetBoundedSlice(this IMemoryGroup group, long start, int length) where T : struct { Guard.NotNull(group, nameof(group)); @@ -55,7 +55,7 @@ namespace SixLabors.ImageSharp.Memory return memory.Slice(bufferStart, length); } - public static void CopyTo(this IMemoryGroup source, Span target) + internal static void CopyTo(this IMemoryGroup source, Span target) where T : struct { Guard.NotNull(source, nameof(source)); @@ -74,11 +74,11 @@ namespace SixLabors.ImageSharp.Memory } } - public static void CopyTo(this Span source, IMemoryGroup target) + internal static void CopyTo(this Span source, IMemoryGroup target) where T : struct => CopyTo((ReadOnlySpan)source, target); - public static void CopyTo(this ReadOnlySpan source, IMemoryGroup target) + internal static void CopyTo(this ReadOnlySpan source, IMemoryGroup target) where T : struct { Guard.NotNull(target, nameof(target)); @@ -95,7 +95,7 @@ namespace SixLabors.ImageSharp.Memory } } - public static void CopyTo(this IMemoryGroup source, IMemoryGroup target) + internal static void CopyTo(this IMemoryGroup source, IMemoryGroup target) where T : struct { Guard.NotNull(source, nameof(source)); @@ -126,7 +126,7 @@ namespace SixLabors.ImageSharp.Memory } } - public static void TransformTo( + internal static void TransformTo( this IMemoryGroup source, IMemoryGroup target, TransformItemsDelegate transform) @@ -161,7 +161,7 @@ namespace SixLabors.ImageSharp.Memory } } - public static void TransformInplace( + internal static void TransformInplace( this IMemoryGroup memoryGroup, TransformItemsInplaceDelegate transform) where T : struct @@ -172,7 +172,7 @@ namespace SixLabors.ImageSharp.Memory } } - public static bool IsEmpty(this IMemoryGroup group) + internal static bool IsEmpty(this IMemoryGroup group) where T : struct => group.Count == 0; diff --git a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs index 316579da7..e44076b3b 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs @@ -349,7 +349,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution { for (int y = rows.Min; y < rows.Max; y++) { - Span targetRowSpan = targetValues.GetRowSpan(y).Slice(startX); + Span targetRowSpan = targetValues.GetRowSpanUnchecked(y).Slice(startX); for (int x = 0; x < width; x++) { @@ -396,7 +396,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution { for (int y = rows.Min; y < rows.Max; y++) { - Span targetRowSpan = targetValues.GetRowSpan(y).Slice(startX); + Span targetRowSpan = targetValues.GetRowSpanUnchecked(y).Slice(startX); for (int x = 0; x < width; x++) { @@ -438,7 +438,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution for (int y = rows.Min; y < rows.Max; y++) { - Span targetRowSpan = targetPixels.GetRowSpan(y).Slice(startX); + Span targetRowSpan = targetPixels.GetRowSpanUnchecked(y).Slice(startX); PixelOperations.Instance.ToVector4(configuration, targetRowSpan.Slice(0, length), vectorSpan, PixelConversionModifiers.Premultiply); ref Vector4 baseRef = ref MemoryMarshal.GetReference(vectorSpan); @@ -489,8 +489,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution for (int y = rows.Min; y < rows.Max; y++) { - Span targetPixelSpan = targetPixels.GetRowSpan(y).Slice(startX); - Span sourceRowSpan = sourceValues.GetRowSpan(y).Slice(startX); + Span targetPixelSpan = targetPixels.GetRowSpanUnchecked(y).Slice(startX); + Span sourceRowSpan = sourceValues.GetRowSpanUnchecked(y).Slice(startX); ref Vector4 sourceRef = ref MemoryMarshal.GetReference(sourceRowSpan); for (int x = 0; x < width; x++) diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs index c2b85a4ab..1e30d6a4a 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs @@ -90,7 +90,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution for (int y = rows.Min; y < rows.Max; y++) { - Span targetRowSpan = targetPixels.GetRowSpan(y).Slice(startX); + Span targetRowSpan = targetPixels.GetRowSpanUnchecked(y).Slice(startX); PixelOperations.Instance.ToVector4(this.Configuration, targetRowSpan.Slice(0, length), vectorSpan); if (preserveAlpha) diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs index 32bdf6bc5..f36d5d6d0 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs @@ -109,7 +109,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution for (int y = rows.Min; y < rows.Max; y++) { - Span targetRowSpan = targetPixels.GetRowSpan(y).Slice(startX); + Span targetRowSpan = targetPixels.GetRowSpanUnchecked(y).Slice(startX); PixelOperations.Instance.ToVector4(configuration, targetRowSpan.Slice(0, length), vectorSpan); if (preserveAlpha) diff --git a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs index 285bcab27..779360bde 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs @@ -80,7 +80,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution for (int y = rows.Min; y < rows.Max; y++) { - Span targetRowSpan = targetPixels.GetRowSpan(y).Slice(startX); + Span targetRowSpan = targetPixels.GetRowSpanUnchecked(y).Slice(startX); PixelOperations.Instance.ToVector4(this.Configuration, targetRowSpan.Slice(0, length), vectorSpan); if (preserveAlpha) diff --git a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs index c1897bed8..29178300e 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs @@ -118,8 +118,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution { int offsetY = y - shiftY; - ref TPixel passPixelsBase = ref MemoryMarshal.GetReference(passPixels.GetRowSpan(offsetY)); - ref TPixel targetPixelsBase = ref MemoryMarshal.GetReference(targetPixels.GetRowSpan(offsetY)); + ref TPixel passPixelsBase = ref MemoryMarshal.GetReference(passPixels.GetRowSpanUnchecked(offsetY)); + ref TPixel targetPixelsBase = ref MemoryMarshal.GetReference(targetPixels.GetRowSpanUnchecked(offsetY)); for (int x = minX; x < maxX; x++) { diff --git a/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs index 472c07aa7..049fb6d02 100644 --- a/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs @@ -153,7 +153,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects } } - Span targetRowAreaPixelSpan = targetPixels.GetRowSpan(y).Slice(startX, rectangleWidth); + Span targetRowAreaPixelSpan = targetPixels.GetRowSpanUnchecked(y).Slice(startX, rectangleWidth); PixelOperations.Instance.FromVector4Destructive(configuration, targetRowAreaVector4Span, targetRowAreaPixelSpan); } diff --git a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor{TPixel}.cs index b5b8cfe56..227d1b969 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor{TPixel}.cs @@ -487,7 +487,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization int y = this.tileYStartPositions[index].y; int endY = Math.Min(y + tileHeight, sourceHeight); ref TPixel sourceBase = ref source.GetPixelReference(0, 0); - ref int cdfMinBase = ref MemoryMarshal.GetReference(this.cdfMinBuffer2D.GetRowSpan(cdfY)); + ref int cdfMinBase = ref MemoryMarshal.GetReference(this.cdfMinBuffer2D.GetRowSpanUnchecked(cdfY)); using (IMemoryOwner histogramBuffer = this.memoryAllocator.Allocate(luminanceLevels)) { @@ -524,7 +524,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.GetRowSpanUnchecked(tileY).Slice(tileX * this.luminanceLevels, this.luminanceLevels); /// /// Remaps the grey value with the cdf. diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs index 06eef76e2..b91a27373 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs @@ -241,7 +241,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms $"Error in KernelMap.CreateKernel({dataRowIndex},{left},{right}): left > this.data.Width"); } - Span rowSpan = this.data.GetRowSpan(dataRowIndex); + Span rowSpan = this.data.GetRowSpanUnchecked(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 57663c07d..ccd36780c 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs @@ -102,7 +102,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms [MethodImpl(MethodImplOptions.AggressiveInlining)] public Span GetColumnSpan(int x, int startY) { - return this.transposedFirstPassBuffer.GetRowSpan(x).Slice(startY - this.currentWindow.Min); + return this.transposedFirstPassBuffer.GetRowSpanUnchecked(x).Slice(startY - this.currentWindow.Min); } public void Initialize() @@ -138,7 +138,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms Unsafe.Add(ref tempRowBase, x) = kernel.ConvolveCore(ref firstPassColumnBase); } - Span targetRowSpan = destination.GetRowSpan(y); + Span targetRowSpan = destination.GetRowSpanUnchecked(y); PixelOperations.Instance.FromVector4Destructive(this.configuration, tempColSpan, targetRowSpan, this.conversionModifiers); } diff --git a/src/ImageSharp/Processing/Processors/Transforms/TransformKernelMap.cs b/src/ImageSharp/Processing/Processors/Transforms/TransformKernelMap.cs index a0d44cb7a..7e261b81e 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/TransformKernelMap.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/TransformKernelMap.cs @@ -53,7 +53,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// The reference to the first item of the window. [MethodImpl(InliningOptions.ShortMethod)] public ref float GetYStartReference(int y) - => ref MemoryMarshal.GetReference(this.yBuffer.GetRowSpan(y)); + => ref MemoryMarshal.GetReference(this.yBuffer.GetRowSpanUnchecked(y)); /// /// Gets a reference to the first item of the x window. @@ -61,7 +61,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// The reference to the first item of the window. [MethodImpl(InliningOptions.ShortMethod)] public ref float GetXStartReference(int y) - => ref MemoryMarshal.GetReference(this.xBuffer.GetRowSpan(y)); + => ref MemoryMarshal.GetReference(this.xBuffer.GetRowSpanUnchecked(y)); public void Convolve( Vector2 transformedPoint, diff --git a/tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsPixel.cs b/tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsPixel.cs index 4241a12f6..68cfa96ed 100644 --- a/tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsPixel.cs +++ b/tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsPixel.cs @@ -78,7 +78,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.GetRowSpanUnchecked(y); this.BulkVectorConvert(span, span, span, amounts.GetSpan()); } @@ -98,7 +98,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.GetRowSpanUnchecked(y); this.BulkPixelConvert(span, span, span, amounts.GetSpan()); } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.ComponentData.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.ComponentData.cs index b9526994e..d97c75dc6 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.ComponentData.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.ComponentData.cs @@ -65,7 +65,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils for (int y = 0; y < result.HeightInBlocks; y++) { - Span blockRow = c.SpectralBlocks.GetRowSpan(y); + Span blockRow = c.SpectralBlocks.GetRowSpanUnchecked(y); for (int x = 0; x < result.WidthInBlocks; x++) { short[] data = blockRow[x].ToArray(); diff --git a/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs b/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs index 03a5f2273..b44e4c903 100644 --- a/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs +++ b/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs @@ -95,7 +95,7 @@ namespace SixLabors.ImageSharp.Tests.Memory using (Buffer2D buffer = this.MemoryAllocator.Allocate2D(width, height)) { - Span span = buffer.GetRowSpan(y); + Span span = buffer.GetRowSpanUnchecked(y); Assert.Equal(width, span.Length); @@ -172,7 +172,7 @@ namespace SixLabors.ImageSharp.Tests.Memory for (int y = 0; y < b.Height; y++) { - Span row = b.GetRowSpan(y); + Span row = b.GetRowSpanUnchecked(y); Span s = row.Slice(startIndex, columnCount); Span d = row.Slice(destIndex, columnCount); @@ -195,7 +195,7 @@ namespace SixLabors.ImageSharp.Tests.Memory for (int y = 0; y < b.Height; y++) { - Span row = b.GetRowSpan(y); + Span row = b.GetRowSpanUnchecked(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 77e899a4c..eeddb7daa 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.GetRowSpanUnchecked(y); Assert.True(row.SequenceEqual(emptyRow)); } } @@ -151,7 +151,7 @@ namespace SixLabors.ImageSharp.Tests.Memory for (int y = area.Rectangle.Y; y < area.Rectangle.Bottom; y++) { - Span span = buffer.GetRowSpan(y).Slice(area.Rectangle.X, area.Width); + Span span = buffer.GetRowSpanUnchecked(y).Slice(area.Rectangle.X, area.Width); Assert.True(span.SequenceEqual(new int[area.Width])); } } From 3b3d3d92e2132ecdc587784020ea681ab4c47af3 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sat, 8 Feb 2020 03:25:28 +0100 Subject: [PATCH 27/62] fix bug found by DetectEdges_WorksOnWrappedMemoryImage --- .../ArrayPoolMemoryAllocator.Buffer{T}.cs | 10 ++++++- src/ImageSharp/Memory/Buffer2D{T}.cs | 15 ++++++---- .../DiscontiguousBuffers/MemoryGroup{T}.cs | 4 ++- .../ImageSharp.Tests/Memory/Buffer2DTests.cs | 29 ++++++++++++++++++- .../MemoryGroupTests.SwapOrCopyContent.cs | 6 ++-- 5 files changed, 53 insertions(+), 11 deletions(-) diff --git a/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.Buffer{T}.cs b/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.Buffer{T}.cs index 0d7e0b784..7a8b4f8bd 100644 --- a/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.Buffer{T}.cs +++ b/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.Buffer{T}.cs @@ -46,7 +46,15 @@ namespace SixLabors.ImageSharp.Memory protected byte[] Data { get; private set; } /// - public override Span GetSpan() => MemoryMarshal.Cast(this.Data.AsSpan()).Slice(0, this.length); + public override Span GetSpan() + { + if (this.Data == null) + { + throw new ObjectDisposedException("ArrayPoolMemoryAllocator.Buffer"); + } + + return MemoryMarshal.Cast(this.Data.AsSpan()).Slice(0, this.length); + } /// protected override void Dispose(bool disposing) diff --git a/src/ImageSharp/Memory/Buffer2D{T}.cs b/src/ImageSharp/Memory/Buffer2D{T}.cs index 29e22767f..ef9898ad3 100644 --- a/src/ImageSharp/Memory/Buffer2D{T}.cs +++ b/src/ImageSharp/Memory/Buffer2D{T}.cs @@ -149,14 +149,14 @@ namespace SixLabors.ImageSharp.Memory /// internal static void SwapOrCopyContent(Buffer2D destination, Buffer2D source) { - MemoryGroup.SwapOrCopyContent(destination.MemoryGroup, source.MemoryGroup); - SwapOwnData(destination, source); + bool swap = MemoryGroup.SwapOrCopyContent(destination.MemoryGroup, source.MemoryGroup); + SwapOwnData(destination, source, swap); } [MethodImpl(InliningOptions.ColdPath)] private Memory GetRowMemorySlow(int y) => this.MemoryGroup.GetBoundedSlice(y * this.Width, this.Width); - private static void SwapOwnData(Buffer2D a, Buffer2D b) + private static void SwapOwnData(Buffer2D a, Buffer2D b, bool swapCachedMemory) { Size aSize = a.Size(); Size bSize = b.Size(); @@ -167,9 +167,12 @@ namespace SixLabors.ImageSharp.Memory a.Width = bSize.Width; a.Height = bSize.Height; - Memory aCached = a.cachedMemory; - a.cachedMemory = b.cachedMemory; - b.cachedMemory = aCached; + if (swapCachedMemory) + { + Memory aCached = a.cachedMemory; + a.cachedMemory = b.cachedMemory; + b.cachedMemory = aCached; + } } } } diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs index 00bd27b34..497f0a354 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs @@ -166,12 +166,13 @@ namespace SixLabors.ImageSharp.Memory /// copies the contents of 'source' to 'target' otherwise (2). /// Groups should be of same TotalLength in case 2. /// - public static void SwapOrCopyContent(MemoryGroup target, MemoryGroup source) + 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 { @@ -182,6 +183,7 @@ namespace SixLabors.ImageSharp.Memory } source.CopyTo(target); + return false; } } } diff --git a/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs b/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs index b44e4c903..36adf9856 100644 --- a/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs +++ b/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs @@ -130,7 +130,7 @@ namespace SixLabors.ImageSharp.Tests.Memory } [Fact] - public void SwapOrCopyContent() + public void SwapOrCopyContent_WhenBothAllocated() { using (Buffer2D a = this.MemoryAllocator.Allocate2D(10, 5, AllocationOptions.Clean)) using (Buffer2D b = this.MemoryAllocator.Allocate2D(3, 7, AllocationOptions.Clean)) @@ -154,6 +154,33 @@ namespace SixLabors.ImageSharp.Tests.Memory } } + [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.GetRowSpanUnchecked(0)[0]; + int actual2 = dest.GetRowSpan(0)[0]; + int actual3 = dest.GetRowMemorySafe(0).Span[0]; + int actual4 = dest.GetRowMemoryFast(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); + } + [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 index b3a522cc6..c10fdc15d 100644 --- a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.SwapOrCopyContent.cs +++ b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.SwapOrCopyContent.cs @@ -24,8 +24,9 @@ namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers Memory b0 = b[0]; Memory b1 = b[1]; - MemoryGroup.SwapOrCopyContent(a, b); + bool swap = MemoryGroup.SwapOrCopyContent(a, b); + Assert.True(swap); Assert.Equal(b0, a[0]); Assert.Equal(b1, a[1]); Assert.Equal(a0, b[0]); @@ -72,9 +73,10 @@ namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers source[0].Span[10] = color; // Act: - MemoryGroup.SwapOrCopyContent(dest, source); + bool swap = MemoryGroup.SwapOrCopyContent(dest, source); // Assert: + Assert.False(swap); Assert.Equal(color, dest[0].Span[10]); Assert.NotEqual(source[0], dest[0]); } From aac5934df6fb9a3bee8121708916b62b519c11e3 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sat, 8 Feb 2020 03:38:20 +0100 Subject: [PATCH 28/62] re-enable all target frameworks --- src/Directory.Build.props | 1 + src/ImageSharp/ImageSharp.csproj | 3 +-- src/ImageSharp/Memory/Buffer2DExtensions.cs | 2 ++ .../Memory/DiscontiguousBuffers/MemoryGroup{T}.cs | 10 +++++----- .../ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj | 3 +-- .../ImageSharp.Tests.ProfilingSandbox.csproj | 3 +-- tests/ImageSharp.Tests/ImageSharp.Tests.csproj | 3 +-- .../Memory/DiscontiguousBuffers/MemoryGroupIndex.cs | 2 +- 8 files changed, 13 insertions(+), 14 deletions(-) diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 27d1d74f8..5e3ad489a 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -23,6 +23,7 @@ + diff --git a/src/ImageSharp/ImageSharp.csproj b/src/ImageSharp/ImageSharp.csproj index 0d803475a..be0e9032b 100644 --- a/src/ImageSharp/ImageSharp.csproj +++ b/src/ImageSharp/ImageSharp.csproj @@ -10,8 +10,7 @@ $(packageversion) 0.0.1 - - netcoreapp3.1 + netcoreapp3.1;netcoreapp2.1;netstandard2.1;netstandard2.0;netstandard1.3;net472 true true diff --git a/src/ImageSharp/Memory/Buffer2DExtensions.cs b/src/ImageSharp/Memory/Buffer2DExtensions.cs index 810d55a77..361132a82 100644 --- a/src/ImageSharp/Memory/Buffer2DExtensions.cs +++ b/src/ImageSharp/Memory/Buffer2DExtensions.cs @@ -39,6 +39,7 @@ namespace SixLabors.ImageSharp.Memory /// Thrown when the backing group is discontiguous. /// // TODO: Review all usages, should be only used with buffers which do not scale fully with image size! + // Remove [Obsolete], when done! [Obsolete("TODO: Review all usages!")] internal static Span GetSingleSpan(this Buffer2D buffer) where T : struct @@ -64,6 +65,7 @@ namespace SixLabors.ImageSharp.Memory /// Thrown when the backing group is discontiguous. /// // TODO: Review all usages, should be only used with buffers which do not scale fully with image size! + // Remove [Obsolete], when done! [Obsolete("TODO: Review all usages!")] internal static Memory GetSingleMemory(this Buffer2D buffer) where T : struct diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs index 497f0a354..38de57b4a 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs @@ -113,7 +113,7 @@ namespace SixLabors.ImageSharp.Memory if (bufferCount > 0) { - buffers[^1] = allocator.Allocate(sizeOfLastBuffer, options); + buffers[buffers.Length - 1] = allocator.Allocate(sizeOfLastBuffer, options); } return new Owned(buffers, bufferLength, totalLength, true); @@ -130,12 +130,12 @@ namespace SixLabors.ImageSharp.Memory } } - if (source.Length > 0 && source[^1].Length > bufferLength) + if (source.Length > 0 && source[source.Length - 1].Length > bufferLength) { throw new InvalidMemoryOperationException("Wrap: the last buffer is too large!"); } - long totalLength = bufferLength > 0 ? ((long)bufferLength * (source.Length - 1)) + source[^1].Length : 0; + long totalLength = bufferLength > 0 ? ((long)bufferLength * (source.Length - 1)) + source[source.Length - 1].Length : 0; return new Consumed(source, bufferLength, totalLength); } @@ -151,12 +151,12 @@ namespace SixLabors.ImageSharp.Memory } } - if (source.Length > 0 && source[^1].Memory.Length > bufferLength) + if (source.Length > 0 && source[source.Length - 1].Memory.Length > bufferLength) { throw new InvalidMemoryOperationException("Wrap: the last buffer is too large!"); } - long totalLength = bufferLength > 0 ? ((long)bufferLength * (source.Length - 1)) + source[^1].Memory.Length : 0; + long totalLength = bufferLength > 0 ? ((long)bufferLength * (source.Length - 1)) + source[source.Length - 1].Memory.Length : 0; return new Owned(source, bufferLength, totalLength, false); } diff --git a/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj b/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj index 198f2fb76..60b1fde8e 100644 --- a/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj +++ b/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj @@ -5,8 +5,7 @@ ImageSharp.Benchmarks Exe SixLabors.ImageSharp.Benchmarks - - netcoreapp3.1 + netcoreapp3.1;netcoreapp2.1;net472 false false diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj b/tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj index f9e6c9da5..7c8031693 100644 --- a/tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj +++ b/tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj @@ -8,8 +8,7 @@ false SixLabors.ImageSharp.Tests.ProfilingSandbox win7-x64 - - netcoreapp3.1 + netcoreapp3.1;netcoreapp2.1;net472 SixLabors.ImageSharp.Tests.ProfilingSandbox.Program false diff --git a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj index fdefa38e7..34cdca49a 100644 --- a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj +++ b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj @@ -2,8 +2,7 @@ - - netcoreapp3.1 + netcoreapp3.1;netcoreapp2.1;net472 True True SixLabors.ImageSharp.Tests diff --git a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupIndex.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupIndex.cs index 158428f4b..555d641c7 100644 --- a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupIndex.cs +++ b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupIndex.cs @@ -114,7 +114,7 @@ namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers { return group.Count == 0 ? new MemoryGroupIndex(group.BufferLength, 0, 0) - : new MemoryGroupIndex(group.BufferLength, group.Count - 1, group[^1].Length); + : new MemoryGroupIndex(group.BufferLength, group.Count - 1, group[group.Count - 1].Length); } } } From 0d0e8b3255d54bf51e91294987cf25cb451085d3 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sat, 8 Feb 2020 04:04:48 +0100 Subject: [PATCH 29/62] remove commented code --- .../Formats/Jpeg/Components/GenericBlock8x8.cs | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.cs b/src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.cs index ebc071494..534c66b99 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.cs @@ -54,19 +54,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components set => this[(y * 8) + x] = value; } - // public void LoadAndStretchEdges(IPixelSource source, int sourceX, RowOctet currentRows) - // where TPixel : struct, IPixel - // { - // if (source.PixelBuffer is Buffer2D buffer) - // { - // this.LoadAndStretchEdges(buffer, sourceX, sourceY); - // } - // else - // { - // throw new InvalidOperationException("LoadAndStretchEdges() is only valid for TPixel == T !"); - // } - // } - /// /// 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. From 31fe2b3d22cfd0ed05f41e349803c6dfc22df6f5 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sat, 8 Feb 2020 04:09:50 +0100 Subject: [PATCH 30/62] no, we still can't cache images for 32bit tests --- .../TestUtilities/ImageProviders/FileProvider.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs index 0427b3732..d94e21609 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs @@ -151,6 +151,11 @@ namespace SixLabors.ImageSharp.Tests { Guard.NotNull(decoder, nameof(decoder)); + if (!TestEnvironment.Is64BitProcess) + { + return this.LoadImage(decoder); + } + int bufferCapacity = this.Configuration.MemoryAllocator.GetBufferCapacityInBytes(); var key = new Key(this.PixelType, this.FilePath, bufferCapacity, decoder); From 59e2537103bfb9328d62f62e40bce664b51b3473 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 9 Feb 2020 21:12:32 +0100 Subject: [PATCH 31/62] Better exceptions for images with degenerate dimensions --- .../Common/Exceptions/ImageFormatException.cs | 2 +- src/ImageSharp/Formats/IImageDecoder.cs | 2 ++ src/ImageSharp/Formats/Jpeg/JpegDecoder.cs | 12 +++++++++++- .../Memory/InvalidMemoryOperationException.cs | 3 ++- .../Formats/Jpg/JpegDecoderTests.Baseline.cs | 3 ++- .../ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs | 12 ++++++++++++ 6 files changed, 30 insertions(+), 4 deletions(-) diff --git a/src/ImageSharp/Common/Exceptions/ImageFormatException.cs b/src/ImageSharp/Common/Exceptions/ImageFormatException.cs index 8b9dbe1b8..4028b70b0 100644 --- a/src/ImageSharp/Common/Exceptions/ImageFormatException.cs +++ b/src/ImageSharp/Common/Exceptions/ImageFormatException.cs @@ -7,7 +7,7 @@ namespace SixLabors.ImageSharp { /// /// The exception that is thrown when the library tries to load - /// an image, which has an invalid format. + /// an image, which has format or content that is invalid or unsupported by ImageSharp. /// public class ImageFormatException : Exception { diff --git a/src/ImageSharp/Formats/IImageDecoder.cs b/src/ImageSharp/Formats/IImageDecoder.cs index e8e84de7d..7188b57a6 100644 --- a/src/ImageSharp/Formats/IImageDecoder.cs +++ b/src/ImageSharp/Formats/IImageDecoder.cs @@ -18,6 +18,7 @@ namespace SixLabors.ImageSharp.Formats /// The configuration for the image. /// The containing image data. /// The . + // TODO: Document ImageFormatExceptions (https://github.com/SixLabors/ImageSharp/issues/1110) Image Decode(Configuration configuration, Stream stream) where TPixel : struct, IPixel; @@ -27,6 +28,7 @@ namespace SixLabors.ImageSharp.Formats /// The configuration for the image. /// The containing image data. /// The . + // TODO: Document ImageFormatExceptions (https://github.com/SixLabors/ImageSharp/issues/1110) Image Decode(Configuration configuration, Stream stream); } } diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs index 4e1c0c1be..187e43269 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System.IO; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Jpeg @@ -22,10 +23,19 @@ namespace SixLabors.ImageSharp.Formats.Jpeg { Guard.NotNull(stream, nameof(stream)); - using (var decoder = new JpegDecoderCore(configuration, this)) + using var decoder = new JpegDecoderCore(configuration, this); + try { return decoder.Decode(stream); } + catch (InvalidMemoryOperationException ex) + { + (int w, int h) = (decoder.ImageWidth, decoder.ImageHeight); + + // TODO: use InvalidImageContentException here, if we decide to define it + // https://github.com/SixLabors/ImageSharp/issues/1110 + throw new ImageFormatException($"Can not decode the image having degenerate dimensions of {w}x{h}.", ex); + } } /// diff --git a/src/ImageSharp/Memory/InvalidMemoryOperationException.cs b/src/ImageSharp/Memory/InvalidMemoryOperationException.cs index 51ed7e861..c1d5c5d41 100644 --- a/src/ImageSharp/Memory/InvalidMemoryOperationException.cs +++ b/src/ImageSharp/Memory/InvalidMemoryOperationException.cs @@ -6,7 +6,8 @@ using System; namespace SixLabors.ImageSharp.Memory { /// - /// Exception thrown on invalid memory (allocation) requests. + /// Exception thrown when the library detects an invalid memory allocation request, + /// or an attempt has been made to use an invalidated . /// public class InvalidMemoryOperationException : InvalidOperationException { diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Baseline.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Baseline.cs index dc4a56195..e237c65c6 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Baseline.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Baseline.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using Microsoft.DotNet.RemoteExecutor; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.TestUtilities; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; @@ -48,7 +49,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [Theory] [WithFileCollection(nameof(UnrecoverableTestJpegs), PixelTypes.Rgba32)] - public void UnrecoverableImagesShouldThrowCorrectError(TestImageProvider provider) + public void UnrecoverableImage_Throws_ImageFormatException(TestImageProvider provider) where TPixel : struct, IPixel => Assert.Throws(provider.GetImage); } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs index d829b5f98..1bf01fd51 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs @@ -105,6 +105,18 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg appendPixelTypeToFileName: false); } + [Theory] + [WithFile(TestImages.Jpeg.Baseline.Floorplan, PixelTypes.Rgba32)] + [WithFile(TestImages.Jpeg.Progressive.Festzug, PixelTypes.Rgba32)] + public void DegenerateMemoryRequest_ShouldTranslateTo_ImageFormatException(TestImageProvider provider) + where TPixel : struct, IPixel + { + provider.LimitAllocatorBufferCapacity(100); + ImageFormatException ex = Assert.Throws(provider.GetImage); + this.Output.WriteLine(ex.Message); + Assert.IsType(ex.InnerException); + } + // DEBUG ONLY! // The PDF.js output should be saved by "tests\ImageSharp.Tests\Formats\Jpg\pdfjs\jpeg-converter.htm" // into "\tests\Images\ActualOutput\JpegDecoderTests\" From 8a5ac95227058a41ad6e8afa5296e8086cabd2ae Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 9 Feb 2020 19:41:31 +0100 Subject: [PATCH 32/62] Remove usage of GetSingleSpan() in bmp decoder --- src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs | 28 ++++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs index c46504cce..392697d15 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs @@ -294,24 +294,27 @@ namespace SixLabors.ImageSharp.Formats.Bmp where TPixel : struct, IPixel { TPixel color = default; - using (Buffer2D buffer = this.memoryAllocator.Allocate2D(width, height, AllocationOptions.Clean)) - using (Buffer2D undefinedPixels = this.memoryAllocator.Allocate2D(width, height, AllocationOptions.Clean)) + using (IMemoryOwner buffer = this.memoryAllocator.Allocate(width * height, AllocationOptions.Clean)) + using (IMemoryOwner undefinedPixels = this.memoryAllocator.Allocate(width * height, AllocationOptions.Clean)) using (IMemoryOwner rowsWithUndefinedPixels = this.memoryAllocator.Allocate(height, AllocationOptions.Clean)) { Span rowsWithUndefinedPixelsSpan = rowsWithUndefinedPixels.Memory.Span; - if (compression == BmpCompression.RLE8) + Span undefinedPixelsSpan = undefinedPixels.Memory.Span; + Span bufferSpan = buffer.Memory.Span; + if (compression is BmpCompression.RLE8) { - this.UncompressRle8(width, buffer.GetSingleSpan(), undefinedPixels.GetSingleSpan(), rowsWithUndefinedPixelsSpan); + this.UncompressRle8(width, bufferSpan, undefinedPixelsSpan, rowsWithUndefinedPixelsSpan); } else { - this.UncompressRle4(width, buffer.GetSingleSpan(), undefinedPixels.GetSingleSpan(), rowsWithUndefinedPixelsSpan); + this.UncompressRle4(width, bufferSpan, undefinedPixelsSpan, rowsWithUndefinedPixelsSpan); } for (int y = 0; y < height; y++) { int newY = Invert(y, height, inverted); - Span bufferRow = buffer.GetRowSpanUnchecked(y); + int rowStartIdx = y * width; + Span bufferRow = bufferSpan.Slice(rowStartIdx, width); Span pixelRow = pixels.GetRowSpanUnchecked(newY); bool rowHasUndefinedPixels = rowsWithUndefinedPixelsSpan[y]; @@ -321,7 +324,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp for (int x = 0; x < width; x++) { byte colorIdx = bufferRow[x]; - if (undefinedPixels[x, y]) + if (undefinedPixelsSpan[rowStartIdx + x]) { switch (this.options.RleSkippedPixelHandling) { @@ -372,12 +375,14 @@ namespace SixLabors.ImageSharp.Formats.Bmp { TPixel color = default; using (IMemoryOwner buffer = this.memoryAllocator.Allocate(width * height * 3, AllocationOptions.Clean)) - using (Buffer2D undefinedPixels = this.memoryAllocator.Allocate2D(width, height, AllocationOptions.Clean)) + using (IMemoryOwner undefinedPixels = this.memoryAllocator.Allocate(width * height, AllocationOptions.Clean)) using (IMemoryOwner rowsWithUndefinedPixels = this.memoryAllocator.Allocate(height, AllocationOptions.Clean)) { Span rowsWithUndefinedPixelsSpan = rowsWithUndefinedPixels.Memory.Span; + Span undefinedPixelsSpan = undefinedPixels.Memory.Span; Span bufferSpan = buffer.GetSpan(); - this.UncompressRle24(width, bufferSpan, undefinedPixels.GetSingleSpan(), rowsWithUndefinedPixelsSpan); + + this.UncompressRle24(width, bufferSpan, undefinedPixelsSpan, rowsWithUndefinedPixelsSpan); for (int y = 0; y < height; y++) { int newY = Invert(y, height, inverted); @@ -386,11 +391,12 @@ namespace SixLabors.ImageSharp.Formats.Bmp if (rowHasUndefinedPixels) { // Slow path with undefined pixels. - int rowStartIdx = y * width * 3; + var yMulWidth = y * width; + int rowStartIdx = yMulWidth * 3; for (int x = 0; x < width; x++) { int idx = rowStartIdx + (x * 3); - if (undefinedPixels[x, y]) + if (undefinedPixelsSpan[yMulWidth + x]) { switch (this.options.RleSkippedPixelHandling) { From ce385e8687f06f67958f2a5b78027b47acb42f3b Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 10 Feb 2020 11:28:52 +0100 Subject: [PATCH 33/62] Remove usage of GetSingleSpan() in tga encoder --- src/ImageSharp/Formats/Tga/TgaEncoderCore.cs | 46 ++++++++++++-------- 1 file changed, 27 insertions(+), 19 deletions(-) diff --git a/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs b/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs index f3451a8e2..5a022e925 100644 --- a/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs +++ b/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs @@ -102,7 +102,7 @@ namespace SixLabors.ImageSharp.Formats.Tga if (this.compression is TgaCompression.RunLength) { - this.WriteRunLengthEndcodedImage(stream, image.Frames.RootFrame); + this.WriteRunLengthEncodedImage(stream, image.Frames.RootFrame); } else { @@ -150,19 +150,20 @@ namespace SixLabors.ImageSharp.Formats.Tga /// The pixel type. /// The stream to write the image to. /// The image to encode. - private void WriteRunLengthEndcodedImage(Stream stream, ImageFrame image) + private void WriteRunLengthEncodedImage(Stream stream, ImageFrame image) where TPixel : struct, IPixel { Rgba32 color = default; Buffer2D pixels = image.PixelBuffer; - Span pixelSpan = pixels.GetSingleSpan(); int totalPixels = image.Width * image.Height; int encodedPixels = 0; while (encodedPixels < totalPixels) { - TPixel currentPixel = pixelSpan[encodedPixels]; + int x = encodedPixels % pixels.Width; + int y = encodedPixels / pixels.Width; + TPixel currentPixel = pixels[x, y]; currentPixel.ToRgba32(ref color); - byte equalPixelCount = this.FindEqualPixels(pixelSpan.Slice(encodedPixels)); + byte equalPixelCount = this.FindEqualPixels(pixels, x, y); // Write the number of equal pixels, with the high bit set, indicating ist a compressed pixel run. stream.WriteByte((byte)(equalPixelCount | 128)); @@ -203,27 +204,34 @@ namespace SixLabors.ImageSharp.Formats.Tga /// Finds consecutive pixels, which have the same value starting from the pixel span offset 0. /// /// The pixel type. - /// The pixel span to search in. + /// The pixels of the image. + /// X coordinate to start searching for the same pixels. + /// Y coordinate to start searching for the same pixels. /// The number of equal pixels. - private byte FindEqualPixels(Span pixelSpan) + private byte FindEqualPixels(Buffer2D pixels, int xStart, int yStart) where TPixel : struct, IPixel { - int idx = 0; byte equalPixelCount = 0; - while (equalPixelCount < 127 && idx < pixelSpan.Length - 1) + for (int y = yStart; y < pixels.Height; y++) { - TPixel currentPixel = pixelSpan[idx]; - TPixel nextPixel = pixelSpan[idx + 1]; - if (currentPixel.Equals(nextPixel)) + for (int x = xStart; x < pixels.Width - 1; x++) { - equalPixelCount++; - } - else - { - return equalPixelCount; + TPixel currentPixel = pixels[x, y]; + TPixel nextPixel = pixels[x + 1, y]; + if (currentPixel.Equals(nextPixel)) + { + equalPixelCount++; + } + else + { + return equalPixelCount; + } + + if (equalPixelCount >= 127) + { + break; + } } - - idx++; } return equalPixelCount; From 9d3d38fb3241e79d5499ecc21ed6a069910df785 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 10 Feb 2020 13:05:01 +0100 Subject: [PATCH 34/62] Fix mistake counting equal pixels for encoding RLE tga --- src/ImageSharp/Formats/Tga/TgaEncoderCore.cs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs b/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs index 5a022e925..9ad5a047e 100644 --- a/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs +++ b/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs @@ -201,7 +201,7 @@ namespace SixLabors.ImageSharp.Formats.Tga } /// - /// Finds consecutive pixels, which have the same value starting from the pixel span offset 0. + /// Finds consecutive pixels which have the same value. /// /// The pixel type. /// The pixels of the image. @@ -212,13 +212,14 @@ namespace SixLabors.ImageSharp.Formats.Tga where TPixel : struct, IPixel { byte equalPixelCount = 0; + bool firstRow = true; + TPixel startPixel = pixels[xStart, yStart]; for (int y = yStart; y < pixels.Height; y++) { - for (int x = xStart; x < pixels.Width - 1; x++) + for (int x = firstRow ? xStart + 1 : 0; x < pixels.Width; x++) { - TPixel currentPixel = pixels[x, y]; - TPixel nextPixel = pixels[x + 1, y]; - if (currentPixel.Equals(nextPixel)) + TPixel nextPixel = pixels[x, y]; + if (startPixel.Equals(nextPixel)) { equalPixelCount++; } @@ -229,9 +230,11 @@ namespace SixLabors.ImageSharp.Formats.Tga if (equalPixelCount >= 127) { - break; + return equalPixelCount; } } + + firstRow = false; } return equalPixelCount; From 7f42381204e26707e25d7cf54edbba18edc26379 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 10 Feb 2020 19:44:08 +0100 Subject: [PATCH 35/62] Add tests for discontiguous buffers for bitmaps --- .../Formats/Bmp/BmpDecoderTests.cs | 97 +++++++++++++------ .../Formats/Bmp/BmpEncoderTests.cs | 10 ++ 2 files changed, 76 insertions(+), 31 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs index fb3348be7..afe2648f5 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs @@ -3,8 +3,12 @@ using System; using System.IO; +using Microsoft.DotNet.RemoteExecutor; + using SixLabors.ImageSharp.Formats.Bmp; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests.TestUtilities; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; @@ -24,6 +28,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp public static readonly string[] BitfieldsBmpFiles = BitFields; + private static BmpDecoder BmpDecoder => new BmpDecoder(); + public static readonly TheoryData RatioFiles = new TheoryData { @@ -33,18 +39,47 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp }; [Theory] - [WithFileCollection(nameof(MiscBmpFiles), PixelTypes.Rgba32)] - public void BmpDecoder_CanDecode_MiscellaneousBitmaps(TestImageProvider provider) + [WithFileCollection(nameof(MiscBmpFiles), PixelTypes.Rgba32, false)] + [WithFileCollection(nameof(MiscBmpFiles), PixelTypes.Rgba32, true)] + public void BmpDecoder_CanDecode_MiscellaneousBitmaps(TestImageProvider provider, bool enforceDiscontiguousBuffers) where TPixel : struct, IPixel { - using (Image image = provider.GetImage(new BmpDecoder())) + static void RunTest(string providerDump, string nonContiguousBuffersStr) { - image.DebugSave(provider); + TestImageProvider provider = BasicSerializer.Deserialize>(providerDump); + + if (!string.IsNullOrEmpty(nonContiguousBuffersStr)) + { + provider.LimitAllocatorBufferCapacity(); + } + + using Image image = provider.GetImage(BmpDecoder); + image.DebugSave(provider, testOutputDetails: nonContiguousBuffersStr); + if (TestEnvironment.IsWindows) { image.CompareToOriginal(provider); } } + + string providerDump = BasicSerializer.Serialize(provider); + RemoteExecutor.Invoke( + RunTest, + providerDump, + enforceDiscontiguousBuffers ? "Disco" : string.Empty) + .Dispose(); + } + + // TODO: A InvalidMemoryOperationException is thrown here, review the thrown exception. + [Theory(Skip = "Review Exception")] + [WithFile(Bit32Rgb, PixelTypes.Rgba32)] + [WithFile(Bit16, PixelTypes.Rgba32)] + public void BmpDecoder_DegenerateMemoryRequest_ShouldTranslateTo_ImageFormatException(TestImageProvider provider) + where TPixel : struct, IPixel + { + provider.LimitAllocatorBufferCapacity(100); + ImageFormatException ex = Assert.Throws(provider.GetImage); + Assert.IsType(ex.InnerException); } [Theory] @@ -52,7 +87,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp public void BmpDecoder_CanDecodeBitfields(TestImageProvider provider) where TPixel : struct, IPixel { - using (Image image = provider.GetImage(new BmpDecoder())) + using (Image image = provider.GetImage(BmpDecoder)) { image.DebugSave(provider); image.CompareToOriginal(provider); @@ -65,7 +100,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp public void BmpDecoder_CanDecode_Inverted(TestImageProvider provider) where TPixel : struct, IPixel { - using (Image image = provider.GetImage(new BmpDecoder())) + using (Image image = provider.GetImage(BmpDecoder)) { image.DebugSave(provider); image.CompareToOriginal(provider); @@ -78,7 +113,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp public void BmpDecoder_CanDecode_1Bit(TestImageProvider provider) where TPixel : struct, IPixel { - using (Image image = provider.GetImage(new BmpDecoder())) + using (Image image = provider.GetImage(BmpDecoder)) { image.DebugSave(provider); image.CompareToOriginal(provider, new SystemDrawingReferenceDecoder()); @@ -90,7 +125,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp public void BmpDecoder_CanDecode_4Bit(TestImageProvider provider) where TPixel : struct, IPixel { - using (Image image = provider.GetImage(new BmpDecoder())) + using (Image image = provider.GetImage(BmpDecoder)) { image.DebugSave(provider); @@ -107,7 +142,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp public void BmpDecoder_CanDecode_8Bit(TestImageProvider provider) where TPixel : struct, IPixel { - using (Image image = provider.GetImage(new BmpDecoder())) + using (Image image = provider.GetImage(BmpDecoder)) { image.DebugSave(provider); image.CompareToOriginal(provider); @@ -119,7 +154,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp public void BmpDecoder_CanDecode_16Bit(TestImageProvider provider) where TPixel : struct, IPixel { - using (Image image = provider.GetImage(new BmpDecoder())) + using (Image image = provider.GetImage(BmpDecoder)) { image.DebugSave(provider); image.CompareToOriginal(provider); @@ -131,7 +166,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp public void BmpDecoder_CanDecode_32Bit(TestImageProvider provider) where TPixel : struct, IPixel { - using (Image image = provider.GetImage(new BmpDecoder())) + using (Image image = provider.GetImage(BmpDecoder)) { image.DebugSave(provider); image.CompareToOriginal(provider); @@ -143,7 +178,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp public void BmpDecoder_CanDecode_32BitV4Header_Fast(TestImageProvider provider) where TPixel : struct, IPixel { - using (Image image = provider.GetImage(new BmpDecoder())) + using (Image image = provider.GetImage(BmpDecoder)) { image.DebugSave(provider); image.CompareToOriginal(provider); @@ -251,7 +286,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp public void BmpDecoder_CanDecodeAlphaBitfields(TestImageProvider provider) where TPixel : struct, IPixel { - using (Image image = provider.GetImage(new BmpDecoder())) + using (Image image = provider.GetImage(BmpDecoder)) { image.DebugSave(provider); @@ -265,7 +300,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp public void BmpDecoder_CanDecodeBitmap_WithAlphaChannel(TestImageProvider provider) where TPixel : struct, IPixel { - using (Image image = provider.GetImage(new BmpDecoder())) + using (Image image = provider.GetImage(BmpDecoder)) { image.DebugSave(provider); image.CompareToOriginal(provider, new MagickReferenceDecoder()); @@ -277,7 +312,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp public void BmpDecoder_CanDecodeBitfields_WithUnusualBitmasks(TestImageProvider provider) where TPixel : struct, IPixel { - using (Image image = provider.GetImage(new BmpDecoder())) + using (Image image = provider.GetImage(BmpDecoder)) { image.DebugSave(provider); @@ -296,7 +331,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp public void BmpDecoder_CanDecodeBmpv2(TestImageProvider provider) where TPixel : struct, IPixel { - using (Image image = provider.GetImage(new BmpDecoder())) + using (Image image = provider.GetImage(BmpDecoder)) { image.DebugSave(provider); image.CompareToOriginal(provider); @@ -308,7 +343,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp public void BmpDecoder_CanDecodeBmpv3(TestImageProvider provider) where TPixel : struct, IPixel { - using (Image image = provider.GetImage(new BmpDecoder())) + using (Image image = provider.GetImage(BmpDecoder)) { image.DebugSave(provider); image.CompareToOriginal(provider); @@ -320,7 +355,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp public void BmpDecoder_CanDecodeLessThanFullPalette(TestImageProvider provider) where TPixel : struct, IPixel { - using (Image image = provider.GetImage(new BmpDecoder())) + using (Image image = provider.GetImage(BmpDecoder)) { image.DebugSave(provider); image.CompareToOriginal(provider, new MagickReferenceDecoder()); @@ -333,7 +368,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp public void BmpDecoder_CanDecodeOversizedPalette(TestImageProvider provider) where TPixel : struct, IPixel { - using (Image image = provider.GetImage(new BmpDecoder())) + using (Image image = provider.GetImage(BmpDecoder)) { image.DebugSave(provider); if (TestEnvironment.IsWindows) @@ -350,7 +385,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp { Assert.Throws(() => { - using (provider.GetImage(new BmpDecoder())) + using (provider.GetImage(BmpDecoder)) { } }); @@ -364,7 +399,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp { Assert.Throws(() => { - using (provider.GetImage(new BmpDecoder())) + using (provider.GetImage(BmpDecoder)) { } }); @@ -375,7 +410,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp public void BmpDecoder_CanDecodeAdobeBmpv3(TestImageProvider provider) where TPixel : struct, IPixel { - using (Image image = provider.GetImage(new BmpDecoder())) + using (Image image = provider.GetImage(BmpDecoder)) { image.DebugSave(provider); image.CompareToOriginal(provider, new MagickReferenceDecoder()); @@ -387,7 +422,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp public void BmpDecoder_CanDecodeAdobeBmpv3_WithAlpha(TestImageProvider provider) where TPixel : struct, IPixel { - using (Image image = provider.GetImage(new BmpDecoder())) + using (Image image = provider.GetImage(BmpDecoder)) { image.DebugSave(provider); image.CompareToOriginal(provider, new MagickReferenceDecoder()); @@ -399,7 +434,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp public void BmpDecoder_CanDecodeBmpv4(TestImageProvider provider) where TPixel : struct, IPixel { - using (Image image = provider.GetImage(new BmpDecoder())) + using (Image image = provider.GetImage(BmpDecoder)) { image.DebugSave(provider); image.CompareToOriginal(provider); @@ -412,7 +447,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp public void BmpDecoder_CanDecodeBmpv5(TestImageProvider provider) where TPixel : struct, IPixel { - using (Image image = provider.GetImage(new BmpDecoder())) + using (Image image = provider.GetImage(BmpDecoder)) { image.DebugSave(provider); image.CompareToOriginal(provider); @@ -424,7 +459,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp public void BmpDecoder_RespectsFileHeaderOffset(TestImageProvider provider) where TPixel : struct, IPixel { - using (Image image = provider.GetImage(new BmpDecoder())) + using (Image image = provider.GetImage(BmpDecoder)) { image.DebugSave(provider); image.CompareToOriginal(provider); @@ -436,7 +471,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp public void BmpDecoder_IsNotBoundToSinglePixelType(TestImageProvider provider) where TPixel : struct, IPixel { - using (Image image = provider.GetImage(new BmpDecoder())) + using (Image image = provider.GetImage(BmpDecoder)) { image.DebugSave(provider); image.CompareToOriginal(provider); @@ -448,7 +483,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp public void BmpDecoder_CanDecode4BytePerEntryPalette(TestImageProvider provider) where TPixel : struct, IPixel { - using (Image image = provider.GetImage(new BmpDecoder())) + using (Image image = provider.GetImage(BmpDecoder)) { image.DebugSave(provider); image.CompareToOriginal(provider); @@ -523,7 +558,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp public void BmpDecoder_CanDecode_Os2v2XShortHeader(TestImageProvider provider) where TPixel : struct, IPixel { - using (Image image = provider.GetImage(new BmpDecoder())) + using (Image image = provider.GetImage(BmpDecoder)) { image.DebugSave(provider); @@ -537,7 +572,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp public void BmpDecoder_CanDecode_Os2v2Header(TestImageProvider provider) where TPixel : struct, IPixel { - using (Image image = provider.GetImage(new BmpDecoder())) + using (Image image = provider.GetImage(BmpDecoder)) { image.DebugSave(provider); @@ -561,7 +596,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp public void BmpDecoder_CanDecode_Os2BitmapArray(TestImageProvider provider) where TPixel : struct, IPixel { - using (Image image = provider.GetImage(new BmpDecoder())) + using (Image image = provider.GetImage(BmpDecoder)) { image.DebugSave(provider); diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs index 55d31b5a3..1788943b2 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs @@ -240,6 +240,16 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp public void Encode_PreservesAlpha(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) where TPixel : struct, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: true); + [Theory] + [WithFile(Car, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel32)] + [WithFile(V5Header, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel32)] + public void Encode_WorksWithDiscontiguousBuffers(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) + where TPixel : struct, IPixel + { + provider.LimitAllocatorBufferCapacity(); + TestBmpEncoderCore(provider, bitsPerPixel); + } + private static void TestBmpEncoderCore( TestImageProvider provider, BmpBitsPerPixel bitsPerPixel, From 9661224fb0166d7a9ad3eadd5dce5a41bd83a027 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 10 Feb 2020 19:45:34 +0100 Subject: [PATCH 36/62] Add tests for discontiguous buffers for tga --- .../Formats/Tga/TgaDecoderTests.cs | 47 ++++++++++++++++++- .../Formats/Tga/TgaEncoderTests.cs | 10 ++++ 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs index 1f8cbd6a9..d87407ad8 100644 --- a/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs @@ -1,9 +1,12 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using Microsoft.DotNet.RemoteExecutor; + using SixLabors.ImageSharp.Formats.Tga; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; - +using SixLabors.ImageSharp.Tests.TestUtilities; using Xunit; // ReSharper disable InconsistentNaming @@ -192,5 +195,47 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga TgaTestUtils.CompareWithReferenceDecoder(provider, image); } } + + // TODO: A InvalidMemoryOperationException is thrown here, review the thrown exception. + [Theory(Skip = "Review Exception")] + [WithFile(Bit16, PixelTypes.Rgba32)] + [WithFile(Bit24, PixelTypes.Rgba32)] + [WithFile(Bit32, PixelTypes.Rgba32)] + public void TgaDecoder_DegenerateMemoryRequest_ShouldTranslateTo_ImageFormatException(TestImageProvider provider) + where TPixel : struct, IPixel + { + provider.LimitAllocatorBufferCapacity(100); + ImageFormatException ex = Assert.Throws(provider.GetImage); + Assert.IsType(ex.InnerException); + } + + [Theory] + [WithFile(Bit24, PixelTypes.Rgba32)] + [WithFile(Bit32, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_WithLimitedAllocatorBufferCapacity(TestImageProvider provider) + where TPixel : struct, IPixel + { + static void RunTest(string providerDump, string nonContiguousBuffersStr) + { + TestImageProvider provider = BasicSerializer.Deserialize>(providerDump); + + provider.LimitAllocatorBufferCapacity(); + + using Image image = provider.GetImage(new TgaDecoder()); + image.DebugSave(provider, testOutputDetails: nonContiguousBuffersStr); + + if (TestEnvironment.IsWindows) + { + image.CompareToOriginal(provider); + } + } + + string providerDump = BasicSerializer.Serialize(provider); + RemoteExecutor.Invoke( + RunTest, + providerDump, + "Disco") + .Dispose(); + } } } diff --git a/tests/ImageSharp.Tests/Formats/Tga/TgaEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Tga/TgaEncoderTests.cs index 26fe7cbda..4144038e4 100644 --- a/tests/ImageSharp.Tests/Formats/Tga/TgaEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tga/TgaEncoderTests.cs @@ -122,6 +122,16 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga public void Encode_Bit32_WithRunLengthEncoding_Works(TestImageProvider provider, TgaBitsPerPixel bitsPerPixel = TgaBitsPerPixel.Pixel32) where TPixel : struct, IPixel => TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.RunLength); + [Theory] + [WithFile(Bit32, PixelTypes.Rgba32, TgaBitsPerPixel.Pixel32)] + [WithFile(Bit24, PixelTypes.Rgba32, TgaBitsPerPixel.Pixel24)] + public void Encode_WorksWithDiscontiguousBuffers(TestImageProvider provider, TgaBitsPerPixel bitsPerPixel) + where TPixel : struct, IPixel + { + provider.LimitAllocatorBufferCapacity(10000); + TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.RunLength); + } + private static void TestTgaEncoderCore( TestImageProvider provider, TgaBitsPerPixel bitsPerPixel, From 8be58fa1257be4f24de80ddc19855ea6f10acaa4 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 10 Feb 2020 19:46:10 +0100 Subject: [PATCH 37/62] Remove not needed png file from the testfiles --- tests/Images/Input/Tga/targa.png | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 tests/Images/Input/Tga/targa.png diff --git a/tests/Images/Input/Tga/targa.png b/tests/Images/Input/Tga/targa.png deleted file mode 100644 index c4933c0eb..000000000 --- a/tests/Images/Input/Tga/targa.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:abc382cec34a04815bd53ad30707c6cdeeceece8731244244e2ab91026d60957 -size 106139 From 1366168a6c37e88c5fec66d1a8053e30d75f71b8 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Tue, 11 Feb 2020 00:32:41 +0100 Subject: [PATCH 38/62] tests improvements: - add failing tests for BmpDecoder - more sophisticated and verbose buffer capacity configurator --- .../Advanced/AdvancedImageExtensions.cs | 2 +- src/ImageSharp/Formats/Bmp/BmpDecoder.cs | 18 ++++++++-- src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs | 2 ++ src/ImageSharp/Formats/Jpeg/JpegDecoder.cs | 2 +- .../Advanced/AdvancedImageExtensionsTests.cs | 6 ++-- .../Formats/Bmp/BmpDecoderTests.cs | 33 ++++++++++++----- .../Formats/Jpg/JpegDecoderTests.Baseline.cs | 2 +- .../Jpg/JpegDecoderTests.Progressive.cs | 2 +- .../Formats/Jpg/JpegDecoderTests.cs | 2 +- .../Formats/Jpg/JpegEncoderTests.cs | 2 +- .../Formats/Tga/TgaDecoderTests.cs | 4 +-- .../Formats/Tga/TgaEncoderTests.cs | 2 +- .../TestUtilities/TestImageExtensions.cs | 35 ++++++++++++++++--- 13 files changed, 85 insertions(+), 27 deletions(-) diff --git a/src/ImageSharp/Advanced/AdvancedImageExtensions.cs b/src/ImageSharp/Advanced/AdvancedImageExtensions.cs index fd9f98ac9..ba50f176e 100644 --- a/src/ImageSharp/Advanced/AdvancedImageExtensions.cs +++ b/src/ImageSharp/Advanced/AdvancedImageExtensions.cs @@ -96,7 +96,7 @@ namespace SixLabors.ImageSharp.Advanced IMemoryGroup mg = source.GetPixelMemoryGroup(); if (mg.Count > 1) { - throw new InvalidOperationException($"GetPixelSpan is invalid, since the backing buffer of this {source.Width}x{source.Height} sized image is discontiguos!"); + throw new InvalidOperationException($"GetPixelSpan is invalid, since the backing buffer of this {source.Width}x{source.Height} sized image is discontiguous!"); } return mg.Single().Span; diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoder.cs b/src/ImageSharp/Formats/Bmp/BmpDecoder.cs index a404ab418..150ce243e 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoder.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoder.cs @@ -1,7 +1,8 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.IO; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Bmp @@ -32,7 +33,20 @@ namespace SixLabors.ImageSharp.Formats.Bmp { Guard.NotNull(stream, nameof(stream)); - return new BmpDecoderCore(configuration, this).Decode(stream); + var decoder = new BmpDecoderCore(configuration, this); + + try + { + return decoder.Decode(stream); + } + catch (InvalidMemoryOperationException ex) + { + Size dims = decoder.Dimensions; + + // TODO: use InvalidImageContentException here, if we decide to define it + // https://github.com/SixLabors/ImageSharp/issues/1110 + throw new ImageFormatException($"Can not decode image. Failed to allocate buffers for possibly degenerate dimensions: {dims.Width}x{dims.Height}.", ex); + } } /// diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs index 392697d15..960c2ecd2 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs @@ -114,6 +114,8 @@ namespace SixLabors.ImageSharp.Formats.Bmp this.options = options; } + public Size Dimensions => new Size(this.infoHeader.Width, this.infoHeader.Height); + /// /// Decodes the image from the specified this._stream and sets /// the data to image. diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs index 187e43269..31085dbaa 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs @@ -34,7 +34,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg // TODO: use InvalidImageContentException here, if we decide to define it // https://github.com/SixLabors/ImageSharp/issues/1110 - throw new ImageFormatException($"Can not decode the image having degenerate dimensions of {w}x{h}.", ex); + throw new ImageFormatException($"Can not decode image. Failed to allocate buffers for possibly degenerate dimensions: {w}x{h}.", ex); } } diff --git a/tests/ImageSharp.Tests/Advanced/AdvancedImageExtensionsTests.cs b/tests/ImageSharp.Tests/Advanced/AdvancedImageExtensionsTests.cs index de69d7207..d40d6ec4c 100644 --- a/tests/ImageSharp.Tests/Advanced/AdvancedImageExtensionsTests.cs +++ b/tests/ImageSharp.Tests/Advanced/AdvancedImageExtensionsTests.cs @@ -25,7 +25,7 @@ namespace SixLabors.ImageSharp.Tests.Advanced public void OwnedMemory_PixelDataIsCorrect(TestImageProvider provider) where TPixel : struct, IPixel { - provider.LimitAllocatorBufferCapacity(); + provider.LimitAllocatorBufferCapacity().InPixels(200); using Image image = provider.GetImage(); @@ -106,7 +106,7 @@ namespace SixLabors.ImageSharp.Tests.Advanced public void GetPixelRowMemory_PixelDataIsCorrect(TestImageProvider provider) where TPixel : struct, IPixel { - provider.LimitAllocatorBufferCapacity(); + provider.LimitAllocatorBufferCapacity().InPixels(200); using Image image = provider.GetImage(); @@ -147,7 +147,7 @@ namespace SixLabors.ImageSharp.Tests.Advanced public void GetPixelRowSpan_ShouldReferenceSpanOfMemory(TestImageProvider provider) where TPixel : struct, IPixel { - provider.LimitAllocatorBufferCapacity(); + provider.LimitAllocatorBufferCapacity().InPixels(200); using Image image = provider.GetImage(); diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs index afe2648f5..5b8060f06 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs @@ -50,7 +50,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp if (!string.IsNullOrEmpty(nonContiguousBuffersStr)) { - provider.LimitAllocatorBufferCapacity(); + provider.LimitAllocatorBufferCapacity().InPixels(100); } using Image image = provider.GetImage(BmpDecoder); @@ -77,7 +77,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp public void BmpDecoder_DegenerateMemoryRequest_ShouldTranslateTo_ImageFormatException(TestImageProvider provider) where TPixel : struct, IPixel { - provider.LimitAllocatorBufferCapacity(100); + provider.LimitAllocatorBufferCapacity().InPixels(10); ImageFormatException ex = Assert.Throws(provider.GetImage); Assert.IsType(ex.InnerException); } @@ -253,11 +253,18 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp } [Theory] - [WithFile(RLE8, PixelTypes.Rgba32)] - [WithFile(RLE8Inverted, PixelTypes.Rgba32)] - public void BmpDecoder_CanDecode_RunLengthEncoded_8Bit(TestImageProvider provider) + [WithFile(RLE8, PixelTypes.Rgba32, false)] + [WithFile(RLE8Inverted, PixelTypes.Rgba32, false)] + [WithFile(RLE8, PixelTypes.Rgba32, true)] + [WithFile(RLE8Inverted, PixelTypes.Rgba32, true)] + public void BmpDecoder_CanDecode_RunLengthEncoded_8Bit(TestImageProvider provider, bool enforceDiscontiguousBuffers) where TPixel : struct, IPixel { + if (enforceDiscontiguousBuffers) + { + provider.LimitAllocatorBufferCapacity().InBytes(100); + } + using (Image image = provider.GetImage(new BmpDecoder { RleSkippedPixelHandling = RleSkippedPixelHandling.FirstColorOfPalette })) { image.DebugSave(provider); @@ -266,12 +273,20 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp } [Theory] - [WithFile(RLE24, PixelTypes.Rgba32)] - [WithFile(RLE24Cut, PixelTypes.Rgba32)] - [WithFile(RLE24Delta, PixelTypes.Rgba32)] - public void BmpDecoder_CanDecode_RunLengthEncoded_24Bit(TestImageProvider provider) + [WithFile(RLE24, PixelTypes.Rgba32, false)] + [WithFile(RLE24Cut, PixelTypes.Rgba32, false)] + [WithFile(RLE24Delta, PixelTypes.Rgba32, false)] + [WithFile(RLE24, PixelTypes.Rgba32, true)] + [WithFile(RLE24Cut, PixelTypes.Rgba32, true)] + [WithFile(RLE24Delta, PixelTypes.Rgba32, true)] + public void BmpDecoder_CanDecode_RunLengthEncoded_24Bit(TestImageProvider provider, bool enforceNonContiguous) where TPixel : struct, IPixel { + if (enforceNonContiguous) + { + provider.LimitAllocatorBufferCapacity().InBytes(50); + } + using (Image image = provider.GetImage(new BmpDecoder { RleSkippedPixelHandling = RleSkippedPixelHandling.Black })) { image.DebugSave(provider); diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Baseline.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Baseline.cs index e237c65c6..99fd6b221 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Baseline.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Baseline.cs @@ -26,7 +26,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg if (!string.IsNullOrEmpty(nonContiguousBuffersStr)) { - provider.LimitAllocatorBufferCapacity(); + provider.LimitAllocatorBufferCapacity().InBytes(200); } using Image image = provider.GetImage(JpegDecoder); diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Progressive.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Progressive.cs index 0755f79d1..5d94ed985 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Progressive.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Progressive.cs @@ -26,7 +26,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg if (!string.IsNullOrEmpty(nonContiguousBuffersStr)) { - provider.LimitAllocatorBufferCapacity(); + provider.LimitAllocatorBufferCapacity().InBytes(200); } using Image image = provider.GetImage(JpegDecoder); diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs index 1bf01fd51..c083d308d 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs @@ -111,7 +111,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg public void DegenerateMemoryRequest_ShouldTranslateTo_ImageFormatException(TestImageProvider provider) where TPixel : struct, IPixel { - provider.LimitAllocatorBufferCapacity(100); + provider.LimitAllocatorBufferCapacity().InBytes(10); ImageFormatException ex = Assert.Throws(provider.GetImage); this.Output.WriteLine(ex.Message); Assert.IsType(ex.InnerException); diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs index 49ef7f8f8..56dd37874 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs @@ -93,7 +93,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg ? ImageComparer.TolerantPercentage(0.1f) : ImageComparer.TolerantPercentage(5f); - provider.LimitAllocatorBufferCapacity(); + provider.LimitAllocatorBufferCapacity().InBytes(200); TestJpegEncoderCore(provider, subsample, 100, comparer); } diff --git a/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs index d87407ad8..429ff995a 100644 --- a/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs @@ -204,7 +204,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga public void TgaDecoder_DegenerateMemoryRequest_ShouldTranslateTo_ImageFormatException(TestImageProvider provider) where TPixel : struct, IPixel { - provider.LimitAllocatorBufferCapacity(100); + provider.LimitAllocatorBufferCapacity().InPixels(10); ImageFormatException ex = Assert.Throws(provider.GetImage); Assert.IsType(ex.InnerException); } @@ -219,7 +219,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga { TestImageProvider provider = BasicSerializer.Deserialize>(providerDump); - provider.LimitAllocatorBufferCapacity(); + provider.LimitAllocatorBufferCapacity().InPixels(200); using Image image = provider.GetImage(new TgaDecoder()); image.DebugSave(provider, testOutputDetails: nonContiguousBuffersStr); diff --git a/tests/ImageSharp.Tests/Formats/Tga/TgaEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Tga/TgaEncoderTests.cs index 4144038e4..2bb49a93e 100644 --- a/tests/ImageSharp.Tests/Formats/Tga/TgaEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tga/TgaEncoderTests.cs @@ -128,7 +128,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga public void Encode_WorksWithDiscontiguousBuffers(TestImageProvider provider, TgaBitsPerPixel bitsPerPixel) where TPixel : struct, IPixel { - provider.LimitAllocatorBufferCapacity(10000); + provider.LimitAllocatorBufferCapacity().InPixels(100); TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.RunLength); } diff --git a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs index fa5eab20a..12321d38f 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs @@ -666,13 +666,12 @@ namespace SixLabors.ImageSharp.Tests } } - internal static void LimitAllocatorBufferCapacity( - this TestImageProvider provider, - int bufferCapacityInPixels = 40000) // 200 x 200 + internal static AllocatorBufferCapacityConfigurator LimitAllocatorBufferCapacity( + this TestImageProvider provider) where TPixel : struct, IPixel { var allocator = (ArrayPoolMemoryAllocator)provider.Configuration.MemoryAllocator; - allocator.BufferCapacityInBytes = Unsafe.SizeOf() * bufferCapacityInPixels; + return new AllocatorBufferCapacityConfigurator(allocator, Unsafe.SizeOf()); } internal static Image ToGrayscaleImage(this Buffer2D buffer, float scale) @@ -734,4 +733,32 @@ namespace SixLabors.ImageSharp.Tests } } } + + internal class AllocatorBufferCapacityConfigurator + { + private readonly ArrayPoolMemoryAllocator allocator; + private readonly int pixelSizeInBytes; + + public AllocatorBufferCapacityConfigurator(ArrayPoolMemoryAllocator allocator, int pixelSizeInBytes) + { + this.allocator = allocator; + this.pixelSizeInBytes = pixelSizeInBytes; + } + + /// + /// Set the maximum buffer capacity to (areaDimensionBytes x areaDimensionBytes) bytes. + /// + public void InBytes(int areaDimensionBytes) + { + this.allocator.BufferCapacityInBytes = areaDimensionBytes * areaDimensionBytes; + } + + /// + /// Set the maximum buffer capacity to (areaDimensionPixels x areaDimensionPixels x size of the pixel) bytes. + /// + public void InPixels(int areaDimensionPixels) + { + this.allocator.BufferCapacityInBytes = areaDimensionPixels * areaDimensionPixels * this.pixelSizeInBytes; + } + } } From a7b29e13a516266e2f18f519a0a0cdbff8117133 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Tue, 11 Feb 2020 00:34:53 +0100 Subject: [PATCH 39/62] re-enable skipped test --- tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs index 5b8060f06..5b1d35cb5 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs @@ -70,8 +70,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp .Dispose(); } - // TODO: A InvalidMemoryOperationException is thrown here, review the thrown exception. - [Theory(Skip = "Review Exception")] + [Theory] [WithFile(Bit32Rgb, PixelTypes.Rgba32)] [WithFile(Bit16, PixelTypes.Rgba32)] public void BmpDecoder_DegenerateMemoryRequest_ShouldTranslateTo_ImageFormatException(TestImageProvider provider) From 5ade80af3a3e5ef90848302b93270af16e107cab Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Tue, 11 Feb 2020 00:44:02 +0100 Subject: [PATCH 40/62] re-enable skipped test --- tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs index 5b1d35cb5..c28aa6b25 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs @@ -77,7 +77,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp where TPixel : struct, IPixel { provider.LimitAllocatorBufferCapacity().InPixels(10); - ImageFormatException ex = Assert.Throws(provider.GetImage); + ImageFormatException ex = Assert.Throws(() => provider.GetImage(BmpDecoder)); Assert.IsType(ex.InnerException); } From 26d75cbfd46d3f385b67ce5dd38f7cf9bdc7744d Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Tue, 11 Feb 2020 00:58:15 +0100 Subject: [PATCH 41/62] TGA limit --- tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs index 429ff995a..8b0e88220 100644 --- a/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs @@ -219,7 +219,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga { TestImageProvider provider = BasicSerializer.Deserialize>(providerDump); - provider.LimitAllocatorBufferCapacity().InPixels(200); + provider.LimitAllocatorBufferCapacity().InPixels(100); using Image image = provider.GetImage(new TgaDecoder()); image.DebugSave(provider, testOutputDetails: nonContiguousBuffersStr); From 13fe3f7e30b396e1a186e36e4e8d8e3faefec5c5 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Tue, 11 Feb 2020 01:04:09 +0100 Subject: [PATCH 42/62] fix my bug in Encode_WorksWithDiscontiguousBuffers --- tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs index 1788943b2..7f9736530 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs @@ -246,7 +246,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp public void Encode_WorksWithDiscontiguousBuffers(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) where TPixel : struct, IPixel { - provider.LimitAllocatorBufferCapacity(); + provider.LimitAllocatorBufferCapacity().InBytes(100); TestBmpEncoderCore(provider, bitsPerPixel); } From 1191d865bec64f26da62c0c133e607590f7f708e Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 11 Feb 2020 15:31:13 +0100 Subject: [PATCH 43/62] Increase LimitAllocatorBufferCapacity to 400 for RLE tests. Add info to ImageFormatException that this may happen for large RLE images --- src/ImageSharp/Formats/Bmp/BmpDecoder.cs | 2 +- tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoder.cs b/src/ImageSharp/Formats/Bmp/BmpDecoder.cs index 150ce243e..e5546b361 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoder.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoder.cs @@ -45,7 +45,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp // TODO: use InvalidImageContentException here, if we decide to define it // https://github.com/SixLabors/ImageSharp/issues/1110 - throw new ImageFormatException($"Can not decode image. Failed to allocate buffers for possibly degenerate dimensions: {dims.Width}x{dims.Height}.", ex); + throw new ImageFormatException($"Can not decode image. Failed to allocate buffers for possibly degenerate dimensions: {dims.Width}x{dims.Height}. This error can happen for very large RLE bitmaps, which are not supported.", ex); } } diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs index c28aa6b25..264516063 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs @@ -261,7 +261,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp { if (enforceDiscontiguousBuffers) { - provider.LimitAllocatorBufferCapacity().InBytes(100); + provider.LimitAllocatorBufferCapacity().InBytes(400); } using (Image image = provider.GetImage(new BmpDecoder { RleSkippedPixelHandling = RleSkippedPixelHandling.FirstColorOfPalette })) @@ -283,7 +283,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp { if (enforceNonContiguous) { - provider.LimitAllocatorBufferCapacity().InBytes(50); + provider.LimitAllocatorBufferCapacity().InBytes(400); } using (Image image = provider.GetImage(new BmpDecoder { RleSkippedPixelHandling = RleSkippedPixelHandling.Black })) From 4ef1852facecf20c8145c23c62266cebb559ea91 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 11 Feb 2020 15:48:46 +0100 Subject: [PATCH 44/62] Re-enable DegenerateMemoryRequest test, fix Exception to be ImageFormatException --- src/ImageSharp/Formats/Tga/TgaDecoder.cs | 17 ++++++++++++++++- src/ImageSharp/Formats/Tga/TgaDecoderCore.cs | 2 ++ .../Formats/Tga/TgaDecoderTests.cs | 3 +-- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/ImageSharp/Formats/Tga/TgaDecoder.cs b/src/ImageSharp/Formats/Tga/TgaDecoder.cs index b97388773..a6de902b8 100644 --- a/src/ImageSharp/Formats/Tga/TgaDecoder.cs +++ b/src/ImageSharp/Formats/Tga/TgaDecoder.cs @@ -1,7 +1,9 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System; using System.IO; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Tga @@ -17,7 +19,20 @@ namespace SixLabors.ImageSharp.Formats.Tga { Guard.NotNull(stream, nameof(stream)); - return new TgaDecoderCore(configuration, this).Decode(stream); + var decoder = new TgaDecoderCore(configuration, this); + + try + { + return decoder.Decode(stream); + } + catch (InvalidMemoryOperationException ex) + { + Size dims = decoder.Dimensions; + + // TODO: use InvalidImageContentException here, if we decide to define it + // https://github.com/SixLabors/ImageSharp/issues/1110 + throw new ImageFormatException($"Can not decode image. Failed to allocate buffers for possibly degenerate dimensions: {dims.Width}x{dims.Height}.", ex); + } } /// diff --git a/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs b/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs index 5846e88dc..1717df63b 100644 --- a/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs +++ b/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs @@ -61,6 +61,8 @@ namespace SixLabors.ImageSharp.Formats.Tga this.options = options; } + public Size Dimensions => new Size(this.fileHeader.Width, this.fileHeader.Height); + /// /// Decodes the image from the specified stream. /// diff --git a/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs index 8b0e88220..000827474 100644 --- a/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs @@ -196,8 +196,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga } } - // TODO: A InvalidMemoryOperationException is thrown here, review the thrown exception. - [Theory(Skip = "Review Exception")] + [Theory] [WithFile(Bit16, PixelTypes.Rgba32)] [WithFile(Bit24, PixelTypes.Rgba32)] [WithFile(Bit32, PixelTypes.Rgba32)] From a02705a72e64b689fd71ecd0ea9c6cd153b20670 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 11 Feb 2020 16:39:41 +0100 Subject: [PATCH 45/62] Add tests for discontiguous buffers for png --- .../Formats/Png/PngDecoderTests.cs | 63 ++++++++++++++++--- .../Formats/Png/PngEncoderTests.cs | 20 ++++++ 2 files changed, 73 insertions(+), 10 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs index a88962e5f..18a8e9b9f 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs @@ -2,11 +2,16 @@ // Licensed under the Apache License, Version 2.0. using System.IO; +using Microsoft.DotNet.RemoteExecutor; + using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Png; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests.TestUtilities; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; + using Xunit; // ReSharper disable InconsistentNaming @@ -16,6 +21,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png { private const PixelTypes PixelTypes = Tests.PixelTypes.Rgba32 | Tests.PixelTypes.RgbaVector | Tests.PixelTypes.Argb32; + private static PngDecoder PngDecoder => new PngDecoder(); + public static readonly string[] CommonTestImages = { TestImages.Png.Splash, @@ -87,7 +94,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png public void Decode(TestImageProvider provider) where TPixel : struct, IPixel { - using (Image image = provider.GetImage(new PngDecoder())) + using (Image image = provider.GetImage(PngDecoder)) { image.DebugSave(provider); @@ -111,7 +118,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png public void Decode_Interlaced_ImageIsCorrect(TestImageProvider provider) where TPixel : struct, IPixel { - using (Image image = provider.GetImage(new PngDecoder())) + using (Image image = provider.GetImage(PngDecoder)) { image.DebugSave(provider); image.CompareToOriginal(provider, ImageComparer.Exact); @@ -123,7 +130,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png public void Decode_48Bpp(TestImageProvider provider) where TPixel : struct, IPixel { - using (Image image = provider.GetImage(new PngDecoder())) + using (Image image = provider.GetImage(PngDecoder)) { image.DebugSave(provider); image.CompareToOriginal(provider, ImageComparer.Exact); @@ -135,7 +142,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png public void Decode_64Bpp(TestImageProvider provider) where TPixel : struct, IPixel { - using (Image image = provider.GetImage(new PngDecoder())) + using (Image image = provider.GetImage(PngDecoder)) { image.DebugSave(provider); image.CompareToOriginal(provider, ImageComparer.Exact); @@ -147,7 +154,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png public void Decoder_L8bitInterlaced(TestImageProvider provider) where TPixel : struct, IPixel { - using (Image image = provider.GetImage(new PngDecoder())) + using (Image image = provider.GetImage(PngDecoder)) { image.DebugSave(provider); image.CompareToOriginal(provider, ImageComparer.Exact); @@ -159,7 +166,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png public void Decode_L16Bit(TestImageProvider provider) where TPixel : struct, IPixel { - using (Image image = provider.GetImage(new PngDecoder())) + using (Image image = provider.GetImage(PngDecoder)) { image.DebugSave(provider); image.CompareToOriginal(provider, ImageComparer.Exact); @@ -171,7 +178,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png public void Decode_GrayAlpha16Bit(TestImageProvider provider) where TPixel : struct, IPixel { - using (Image image = provider.GetImage(new PngDecoder())) + using (Image image = provider.GetImage(PngDecoder)) { image.DebugSave(provider); image.CompareToOriginal(provider, ImageComparer.Exact); @@ -183,7 +190,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png public void Decoder_CanDecodeGrey8bitWithAlpha(TestImageProvider provider) where TPixel : struct, IPixel { - using (Image image = provider.GetImage(new PngDecoder())) + using (Image image = provider.GetImage(PngDecoder)) { image.DebugSave(provider); image.CompareToOriginal(provider, ImageComparer.Exact); @@ -195,7 +202,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png public void Decoder_IsNotBoundToSinglePixelType(TestImageProvider provider) where TPixel : struct, IPixel { - using (Image image = provider.GetImage(new PngDecoder())) + using (Image image = provider.GetImage(PngDecoder)) { image.DebugSave(provider); image.CompareToOriginal(provider, ImageComparer.Exact); @@ -227,7 +234,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png System.Exception ex = Record.Exception( () => { - using (Image image = provider.GetImage(new PngDecoder())) + using (Image image = provider.GetImage(PngDecoder)) { image.DebugSave(provider); image.CompareToOriginal(provider, ImageComparer.Exact); @@ -235,5 +242,41 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png }); Assert.Null(ex); } + + [Theory] + [WithFile(TestImages.Png.Splash, PixelTypes.Rgba32)] + [WithFile(TestImages.Png.Bike, PixelTypes.Rgba32)] + public void PngDecoder_DegenerateMemoryRequest_ShouldTranslateTo_ImageFormatException(TestImageProvider provider) + where TPixel : struct, IPixel + { + provider.LimitAllocatorBufferCapacity().InPixels(10); + ImageFormatException ex = Assert.Throws(provider.GetImage); + Assert.IsType(ex.InnerException); + } + + [Theory] + [WithFile(TestImages.Png.Splash, PixelTypes.Rgba32)] + [WithFile(TestImages.Png.Bike, PixelTypes.Rgba32)] + public void PngDecoder_CanDecode_WithLimitedAllocatorBufferCapacity(TestImageProvider provider) + where TPixel : struct, IPixel + { + static void RunTest(string providerDump, string nonContiguousBuffersStr) + { + TestImageProvider provider = BasicSerializer.Deserialize>(providerDump); + + provider.LimitAllocatorBufferCapacity().InPixels(100); + + using Image image = provider.GetImage(PngDecoder); + image.DebugSave(provider, testOutputDetails: nonContiguousBuffersStr); + image.CompareToOriginal(provider); + } + + string providerDump = BasicSerializer.Serialize(provider); + RemoteExecutor.Invoke( + RunTest, + providerDump, + "Disco") + .Dispose(); + } } } diff --git a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs index f5b06eb6c..a26bb7353 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs @@ -404,6 +404,26 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png } } + [Theory] + [WithTestPatternImages(587, 821, PixelTypes.Rgba32)] + [WithTestPatternImages(677, 683, PixelTypes.Rgba32)] + public void Encode_WorksWithDiscontiguousBuffers(TestImageProvider provider) + where TPixel : struct, IPixel + { + provider.LimitAllocatorBufferCapacity().InPixels(200); + foreach (PngInterlaceMode interlaceMode in InterlaceMode) + { + TestPngEncoderCore( + provider, + PngColorType.Rgb, + PngFilterMethod.Adaptive, + PngBitDepth.Bit8, + interlaceMode, + appendPixelType: true, + appendPngColorType: true); + } + } + private static void TestPngEncoderCore( TestImageProvider provider, PngColorType pngColorType, From ba4ef9df8025241bcc7a64b0999b70bc379dac26 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 11 Feb 2020 16:42:03 +0100 Subject: [PATCH 46/62] Add tests for discontiguous buffers for png --- src/ImageSharp/Formats/Png/PngDecoder.cs | 15 ++++++++++++++- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 8 +++++--- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/ImageSharp/Formats/Png/PngDecoder.cs b/src/ImageSharp/Formats/Png/PngDecoder.cs index eea9e54c0..3b41cfc6e 100644 --- a/src/ImageSharp/Formats/Png/PngDecoder.cs +++ b/src/ImageSharp/Formats/Png/PngDecoder.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System.IO; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Png @@ -44,7 +45,19 @@ namespace SixLabors.ImageSharp.Formats.Png where TPixel : struct, IPixel { var decoder = new PngDecoderCore(configuration, this); - return decoder.Decode(stream); + + try + { + return decoder.Decode(stream); + } + catch (InvalidMemoryOperationException ex) + { + Size dims = decoder.Dimensions; + + // TODO: use InvalidImageContentException here, if we decide to define it + // https://github.com/SixLabors/ImageSharp/issues/1110 + throw new ImageFormatException($"Can not decode image. Failed to allocate buffers for possibly degenerate dimensions: {dims.Width}x{dims.Height}.", ex); + } } /// diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index 69b341c8d..ff75e4290 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -106,7 +106,7 @@ namespace SixLabors.ImageSharp.Formats.Png private int currentRow = Adam7.FirstRow[0]; /// - /// The current number of bytes read in the current scanline + /// The current number of bytes read in the current scanline. /// private int currentRowBytesRead; @@ -132,18 +132,20 @@ namespace SixLabors.ImageSharp.Formats.Png this.ignoreMetadata = options.IgnoreMetadata; } + public Size Dimensions => new Size(this.header.Width, this.header.Height); + /// /// Decodes the stream to the image. /// /// The pixel format. - /// The stream containing image data. + /// The stream containing image data. /// /// Thrown if the stream does not contain and end chunk. /// /// /// Thrown if the image is larger than the maximum allowable size. /// - /// The decoded image + /// The decoded image. public Image Decode(Stream stream) where TPixel : struct, IPixel { From 7217552ebf7f0b15beb4a0d0f5d6b5695563dc31 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 11 Feb 2020 17:24:38 +0100 Subject: [PATCH 47/62] Add tests for discontiguous buffers for gif --- src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs | 3 ++ src/ImageSharp/Formats/Gif/GifDecoder.cs | 16 +++++++- src/ImageSharp/Formats/Gif/GifDecoderCore.cs | 7 +++- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 3 ++ src/ImageSharp/Formats/Tga/TgaDecoderCore.cs | 3 ++ .../Formats/Gif/GifDecoderTests.cs | 40 +++++++++++++++++++ .../Formats/Gif/GifEncoderTests.cs | 10 ++++- 7 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs index 960c2ecd2..fdd371f54 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs @@ -114,6 +114,9 @@ namespace SixLabors.ImageSharp.Formats.Bmp this.options = options; } + /// + /// Gets the dimensions of the image. + /// public Size Dimensions => new Size(this.infoHeader.Width, this.infoHeader.Height); /// diff --git a/src/ImageSharp/Formats/Gif/GifDecoder.cs b/src/ImageSharp/Formats/Gif/GifDecoder.cs index 7691ec1aa..24e3d8826 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoder.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoder.cs @@ -1,7 +1,9 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System; using System.IO; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; @@ -27,7 +29,19 @@ namespace SixLabors.ImageSharp.Formats.Gif where TPixel : struct, IPixel { var decoder = new GifDecoderCore(configuration, this); - return decoder.Decode(stream); + + try + { + return decoder.Decode(stream); + } + catch (InvalidMemoryOperationException ex) + { + Size dims = decoder.Dimensions; + + // TODO: use InvalidImageContentException here, if we decide to define it + // https://github.com/SixLabors/ImageSharp/issues/1110 + throw new ImageFormatException($"Can not decode image. Failed to allocate buffers for possibly degenerate dimensions: {dims.Width}x{dims.Height}.", ex); + } } /// diff --git a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs index 98dbddb48..bc508cba7 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs @@ -86,10 +86,15 @@ namespace SixLabors.ImageSharp.Formats.Gif public bool IgnoreMetadata { get; internal set; } /// - /// Gets the decoding mode for multi-frame images + /// Gets the decoding mode for multi-frame images. /// public FrameDecodingMode DecodingMode { get; } + /// + /// Gets the dimensions of the image. + /// + public Size Dimensions => new Size(this.imageDescriptor.Width, this.imageDescriptor.Height); + private MemoryAllocator MemoryAllocator => this.configuration.MemoryAllocator; /// diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index ff75e4290..2701bd2a7 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -132,6 +132,9 @@ namespace SixLabors.ImageSharp.Formats.Png this.ignoreMetadata = options.IgnoreMetadata; } + /// + /// Gets the dimensions of the image. + /// public Size Dimensions => new Size(this.header.Width, this.header.Height); /// diff --git a/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs b/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs index 1717df63b..6768f4db3 100644 --- a/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs +++ b/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs @@ -61,6 +61,9 @@ namespace SixLabors.ImageSharp.Formats.Tga this.options = options; } + /// + /// Gets the dimensions of the image. + /// public Size Dimensions => new Size(this.fileHeader.Width, this.fileHeader.Height); /// diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs index 99dc2d06d..ea244f68a 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs @@ -4,10 +4,14 @@ using System; using System.Collections.Generic; using System.IO; +using Microsoft.DotNet.RemoteExecutor; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Formats.Gif; +using SixLabors.ImageSharp.Formats.Tga; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests.TestUtilities; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using Xunit; @@ -163,5 +167,41 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif } } } + + [Theory] + [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32)] + [WithFile(TestImages.Gif.Kumin, PixelTypes.Rgba32)] + public void GifDecoder_DegenerateMemoryRequest_ShouldTranslateTo_ImageFormatException(TestImageProvider provider) + where TPixel : struct, IPixel + { + provider.LimitAllocatorBufferCapacity().InPixels(10); + ImageFormatException ex = Assert.Throws(provider.GetImage); + Assert.IsType(ex.InnerException); + } + + [Theory] + [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32)] + [WithFile(TestImages.Gif.Kumin, PixelTypes.Rgba32)] + public void GifDecoder_CanDecode_WithLimitedAllocatorBufferCapacity(TestImageProvider provider) + where TPixel : struct, IPixel + { + static void RunTest(string providerDump, string nonContiguousBuffersStr) + { + TestImageProvider provider = BasicSerializer.Deserialize>(providerDump); + + provider.LimitAllocatorBufferCapacity().InPixels(100); + + using Image image = provider.GetImage(new GifDecoder()); + image.DebugSave(provider); + image.CompareToOriginal(provider); + } + + string providerDump = BasicSerializer.Serialize(provider); + RemoteExecutor.Invoke( + RunTest, + providerDump, + "Disco") + .Dispose(); + } } } diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs index fe1faa5ae..4c710cdb2 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs @@ -26,10 +26,16 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif }; [Theory] - [WithTestPatternImages(100, 100, TestPixelTypes)] - public void EncodeGeneratedPatterns(TestImageProvider provider) + [WithTestPatternImages(100, 100, TestPixelTypes, false)] + [WithTestPatternImages(100, 100, TestPixelTypes, false)] + public void EncodeGeneratedPatterns(TestImageProvider provider, bool limitAllocationBuffer) where TPixel : struct, IPixel { + if (limitAllocationBuffer) + { + provider.LimitAllocatorBufferCapacity().InPixels(100); + } + using (Image image = provider.GetImage()) { var encoder = new GifEncoder From 02d41bc5bd645927df050e8e0b3ab9e68660095a Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 12 Feb 2020 13:42:50 +1100 Subject: [PATCH 48/62] Fix warnings --- .../Processors/Transforms/AffineTransformProcessor{TPixel}.cs | 2 +- .../Processing/Processors/Transforms/CropProcessor{TPixel}.cs | 2 +- .../Transforms/ProjectiveTransformProcessor{TPixel}.cs | 2 +- .../Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs | 2 +- .../Processing/Processors/Transforms/RotateProcessor{TPixel}.cs | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs index 0d9055f34..574d3cb18 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs @@ -45,7 +45,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms if (this.transformMatrix.Equals(default) || this.transformMatrix.Equals(Matrix3x2.Identity)) { // The clone will be blank here copy all the pixel data over - source.GetPixelSpan().CopyTo(destination.GetPixelSpan()); + source.GetPixelMemoryGroup().CopyTo(destination.GetPixelMemoryGroup()); return; } diff --git a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs index 6ad7aa2a2..e8eeea3cb 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs @@ -41,7 +41,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms && this.SourceRectangle == this.cropRectangle) { // the cloned will be blank here copy all the pixel data over - source.GetPixelSpan().CopyTo(destination.GetPixelSpan()); + source.GetPixelMemoryGroup().CopyTo(destination.GetPixelMemoryGroup()); return; } diff --git a/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs index da071e3f2..175615ebd 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs @@ -45,7 +45,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms if (this.transformMatrix.Equals(default) || this.transformMatrix.Equals(Matrix4x4.Identity)) { // The clone will be blank here copy all the pixel data over - source.GetPixelSpan().CopyTo(destination.GetPixelSpan()); + source.GetPixelMemoryGroup().CopyTo(destination.GetPixelMemoryGroup()); return; } diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs index 53810a5cc..92c1b71fe 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs @@ -81,7 +81,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms && sourceRectangle == this.targetRectangle) { // The cloned will be blank here copy all the pixel data over - source.GetPixelSpan().CopyTo(destination.GetPixelSpan()); + source.GetPixelMemoryGroup().CopyTo(destination.GetPixelMemoryGroup()); return; } diff --git a/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor{TPixel}.cs index 086314a26..198e142d0 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor{TPixel}.cs @@ -98,7 +98,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms if (MathF.Abs(degrees) < Constants.Epsilon) { // The destination will be blank here so copy all the pixel data over - source.GetPixelSpan().CopyTo(destination.GetPixelSpan()); + source.GetPixelMemoryGroup().CopyTo(destination.GetPixelMemoryGroup()); return true; } From ef044099810f8042248219b48d9cf34af39f34df Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 12 Feb 2020 10:27:44 +0100 Subject: [PATCH 49/62] Fix DegenerateMemoryRequest tests --- .../Formats/Gif/GifDecoderTests.cs | 13 +++---- .../Formats/Jpg/JpegDecoderTests.cs | 2 +- .../Formats/Png/PngDecoderTests.cs | 2 +- .../Formats/Tga/TgaDecoderTests.cs | 36 ++++++++++--------- 4 files changed, 28 insertions(+), 25 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs index ea244f68a..2b25f8e87 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs @@ -5,14 +5,15 @@ using System; using System.Collections.Generic; using System.IO; using Microsoft.DotNet.RemoteExecutor; + using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Formats.Gif; -using SixLabors.ImageSharp.Formats.Tga; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.TestUtilities; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; + using Xunit; // ReSharper disable InconsistentNaming @@ -22,6 +23,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif { private const PixelTypes TestPixelTypes = PixelTypes.Rgba32 | PixelTypes.RgbaVector | PixelTypes.Argb32; + private static GifDecoder GifDecoder => new GifDecoder(); + public static readonly string[] MultiFrameTestFiles = { TestImages.Gif.Giphy, TestImages.Gif.Kumin @@ -76,9 +79,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif { using (var stream = new UnmanagedMemoryStream(data, length)) { - var decoder = new GifDecoder(); - - using (Image image = decoder.Decode(Configuration.Default, stream)) + using (Image image = GifDecoder.Decode(Configuration.Default, stream)) { Assert.Equal((200, 200), (image.Width, image.Height)); } @@ -175,7 +176,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif where TPixel : struct, IPixel { provider.LimitAllocatorBufferCapacity().InPixels(10); - ImageFormatException ex = Assert.Throws(provider.GetImage); + ImageFormatException ex = Assert.Throws(() => provider.GetImage(GifDecoder)); Assert.IsType(ex.InnerException); } @@ -191,7 +192,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif provider.LimitAllocatorBufferCapacity().InPixels(100); - using Image image = provider.GetImage(new GifDecoder()); + using Image image = provider.GetImage(GifDecoder); image.DebugSave(provider); image.CompareToOriginal(provider); } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs index c083d308d..97dd4f001 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs @@ -112,7 +112,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg where TPixel : struct, IPixel { provider.LimitAllocatorBufferCapacity().InBytes(10); - ImageFormatException ex = Assert.Throws(provider.GetImage); + ImageFormatException ex = Assert.Throws(() => provider.GetImage(JpegDecoder)); this.Output.WriteLine(ex.Message); Assert.IsType(ex.InnerException); } diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs index 18a8e9b9f..14b29d194 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs @@ -250,7 +250,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png where TPixel : struct, IPixel { provider.LimitAllocatorBufferCapacity().InPixels(10); - ImageFormatException ex = Assert.Throws(provider.GetImage); + ImageFormatException ex = Assert.Throws(() => provider.GetImage(PngDecoder)); Assert.IsType(ex.InnerException); } diff --git a/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs index 000827474..a83ff5d63 100644 --- a/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs @@ -16,12 +16,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga public class TgaDecoderTests { + private static TgaDecoder TgaDecoder => new TgaDecoder(); + [Theory] [WithFile(Grey, PixelTypes.Rgba32)] public void TgaDecoder_CanDecode_Uncompressed_MonoChrome(TestImageProvider provider) where TPixel : struct, IPixel { - using (Image image = provider.GetImage(new TgaDecoder())) + using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); TgaTestUtils.CompareWithReferenceDecoder(provider, image); @@ -33,7 +35,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga public void TgaDecoder_CanDecode_Uncompressed_15Bit(TestImageProvider provider) where TPixel : struct, IPixel { - using (Image image = provider.GetImage(new TgaDecoder())) + using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); TgaTestUtils.CompareWithReferenceDecoder(provider, image); @@ -45,7 +47,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga public void TgaDecoder_CanDecode_RunLengthEncoded_15Bit(TestImageProvider provider) where TPixel : struct, IPixel { - using (Image image = provider.GetImage(new TgaDecoder())) + using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); TgaTestUtils.CompareWithReferenceDecoder(provider, image); @@ -57,7 +59,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga public void TgaDecoder_CanDecode_Uncompressed_16Bit(TestImageProvider provider) where TPixel : struct, IPixel { - using (Image image = provider.GetImage(new TgaDecoder())) + using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); TgaTestUtils.CompareWithReferenceDecoder(provider, image); @@ -69,7 +71,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga public void TgaDecoder_CanDecode_RunLengthEncoded_WithPalette_16Bit(TestImageProvider provider) where TPixel : struct, IPixel { - using (Image image = provider.GetImage(new TgaDecoder())) + using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); TgaTestUtils.CompareWithReferenceDecoder(provider, image); @@ -81,7 +83,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga public void TgaDecoder_CanDecode_Uncompressed_24Bit(TestImageProvider provider) where TPixel : struct, IPixel { - using (Image image = provider.GetImage(new TgaDecoder())) + using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); TgaTestUtils.CompareWithReferenceDecoder(provider, image); @@ -93,7 +95,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga public void TgaDecoder_CanDecode_RunLengthEncoded_WithTopLeftOrigin_24Bit(TestImageProvider provider) where TPixel : struct, IPixel { - using (Image image = provider.GetImage(new TgaDecoder())) + using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); TgaTestUtils.CompareWithReferenceDecoder(provider, image); @@ -105,7 +107,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga public void TgaDecoder_CanDecode_Palette_WithTopLeftOrigin_24Bit(TestImageProvider provider) where TPixel : struct, IPixel { - using (Image image = provider.GetImage(new TgaDecoder())) + using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); TgaTestUtils.CompareWithReferenceDecoder(provider, image); @@ -117,7 +119,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga public void TgaDecoder_CanDecode_Uncompressed_32Bit(TestImageProvider provider) where TPixel : struct, IPixel { - using (Image image = provider.GetImage(new TgaDecoder())) + using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); TgaTestUtils.CompareWithReferenceDecoder(provider, image); @@ -129,7 +131,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga public void TgaDecoder_CanDecode_RunLengthEncoded_MonoChrome(TestImageProvider provider) where TPixel : struct, IPixel { - using (Image image = provider.GetImage(new TgaDecoder())) + using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); TgaTestUtils.CompareWithReferenceDecoder(provider, image); @@ -141,7 +143,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga public void TgaDecoder_CanDecode_RunLengthEncoded_16Bit(TestImageProvider provider) where TPixel : struct, IPixel { - using (Image image = provider.GetImage(new TgaDecoder())) + using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); TgaTestUtils.CompareWithReferenceDecoder(provider, image); @@ -153,7 +155,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga public void TgaDecoder_CanDecode_RunLengthEncoded_24Bit(TestImageProvider provider) where TPixel : struct, IPixel { - using (Image image = provider.GetImage(new TgaDecoder())) + using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); TgaTestUtils.CompareWithReferenceDecoder(provider, image); @@ -165,7 +167,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga public void TgaDecoder_CanDecode_RunLengthEncoded_32Bit(TestImageProvider provider) where TPixel : struct, IPixel { - using (Image image = provider.GetImage(new TgaDecoder())) + using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); TgaTestUtils.CompareWithReferenceDecoder(provider, image); @@ -177,7 +179,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga public void TgaDecoder_CanDecode_WithPalette_16Bit(TestImageProvider provider) where TPixel : struct, IPixel { - using (Image image = provider.GetImage(new TgaDecoder())) + using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); TgaTestUtils.CompareWithReferenceDecoder(provider, image); @@ -189,7 +191,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga public void TgaDecoder_CanDecode_WithPalette_24Bit(TestImageProvider provider) where TPixel : struct, IPixel { - using (Image image = provider.GetImage(new TgaDecoder())) + using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); TgaTestUtils.CompareWithReferenceDecoder(provider, image); @@ -204,7 +206,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga where TPixel : struct, IPixel { provider.LimitAllocatorBufferCapacity().InPixels(10); - ImageFormatException ex = Assert.Throws(provider.GetImage); + ImageFormatException ex = Assert.Throws(() => provider.GetImage(TgaDecoder)); Assert.IsType(ex.InnerException); } @@ -220,7 +222,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga provider.LimitAllocatorBufferCapacity().InPixels(100); - using Image image = provider.GetImage(new TgaDecoder()); + using Image image = provider.GetImage(TgaDecoder); image.DebugSave(provider, testOutputDetails: nonContiguousBuffersStr); if (TestEnvironment.IsWindows) From d36d902456c668525334284b538fce8f86568533 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Thu, 13 Feb 2020 03:20:59 +0100 Subject: [PATCH 50/62] merge back GetRowSpan and GetRowSpanUnchecked --- .../Advanced/AdvancedImageExtensions.cs | 8 +- .../Common/Helpers/Buffer2DUtils.cs | 4 +- .../Common/Helpers/DenseMatrixUtils.cs | 4 +- src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs | 18 ++-- src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs | 6 +- .../ColorConverters/JpegColorConverter.cs | 8 +- .../Components/Decoder/HuffmanScanDecoder.cs | 10 +- .../Decoder/JpegComponentPostProcessor.cs | 2 +- .../Formats/Jpeg/Components/RowOctet.cs | 16 ++-- src/ImageSharp/Formats/Tga/TgaDecoderCore.cs | 14 +-- src/ImageSharp/Formats/Tga/TgaEncoderCore.cs | 8 +- src/ImageSharp/ImageFrame{TPixel}.cs | 36 ++++++- src/ImageSharp/Image{TPixel}.cs | 36 ++++++- src/ImageSharp/Memory/Buffer2D{T}.cs | 60 +++++++----- .../Convolution/BokehBlurProcessor{TPixel}.cs | 10 +- .../Convolution2DProcessor{TPixel}.cs | 2 +- .../Convolution2PassProcessor{TPixel}.cs | 2 +- .../ConvolutionProcessor{TPixel}.cs | 2 +- .../EdgeDetectorCompassProcessor{TPixel}.cs | 4 +- .../Effects/OilPaintingProcessor{TPixel}.cs | 2 +- ...eHistogramEqualizationProcessor{TPixel}.cs | 6 +- .../Transforms/Resize/ResizeKernelMap.cs | 2 +- .../Transforms/Resize/ResizeWorker.cs | 4 +- .../Transforms/TransformKernelMap.cs | 4 +- .../PixelBlenders/PorterDuffBulkVsPixel.cs | 4 +- .../Jpg/Utils/LibJpegTools.ComponentData.cs | 2 +- .../ImageSharp.Tests/Image/ImageFrameTests.cs | 96 +++++++++++++++++++ tests/ImageSharp.Tests/Image/ImageTests.cs | 81 ++++++++++++++++ .../ImageSharp.Tests/Memory/Buffer2DTests.cs | 55 +++++++++-- .../Memory/BufferAreaTests.cs | 4 +- 30 files changed, 402 insertions(+), 108 deletions(-) create mode 100644 tests/ImageSharp.Tests/Image/ImageFrameTests.cs diff --git a/src/ImageSharp/Advanced/AdvancedImageExtensions.cs b/src/ImageSharp/Advanced/AdvancedImageExtensions.cs index ba50f176e..6bd65022e 100644 --- a/src/ImageSharp/Advanced/AdvancedImageExtensions.cs +++ b/src/ImageSharp/Advanced/AdvancedImageExtensions.cs @@ -135,7 +135,7 @@ namespace SixLabors.ImageSharp.Advanced Guard.MustBeGreaterThanOrEqualTo(rowIndex, 0, nameof(rowIndex)); Guard.MustBeLessThan(rowIndex, source.Height, nameof(rowIndex)); - return source.PixelBuffer.GetRowSpanUnchecked(rowIndex); + return source.PixelBuffer.GetRowSpan(rowIndex); } /// @@ -153,7 +153,7 @@ namespace SixLabors.ImageSharp.Advanced Guard.MustBeGreaterThanOrEqualTo(rowIndex, 0, nameof(rowIndex)); Guard.MustBeLessThan(rowIndex, source.Height, nameof(rowIndex)); - return source.Frames.RootFrame.PixelBuffer.GetRowSpanUnchecked(rowIndex); + return source.Frames.RootFrame.PixelBuffer.GetRowSpan(rowIndex); } /// @@ -171,7 +171,7 @@ namespace SixLabors.ImageSharp.Advanced Guard.MustBeGreaterThanOrEqualTo(rowIndex, 0, nameof(rowIndex)); Guard.MustBeLessThan(rowIndex, source.Height, nameof(rowIndex)); - return source.PixelBuffer.GetRowMemorySafe(rowIndex); + return source.PixelBuffer.GetSafeRowMemory(rowIndex); } /// @@ -189,7 +189,7 @@ namespace SixLabors.ImageSharp.Advanced Guard.MustBeGreaterThanOrEqualTo(rowIndex, 0, nameof(rowIndex)); Guard.MustBeLessThan(rowIndex, source.Height, nameof(rowIndex)); - return source.Frames.RootFrame.PixelBuffer.GetRowMemorySafe(rowIndex); + return source.Frames.RootFrame.PixelBuffer.GetSafeRowMemory(rowIndex); } /// diff --git a/src/ImageSharp/Common/Helpers/Buffer2DUtils.cs b/src/ImageSharp/Common/Helpers/Buffer2DUtils.cs index 677520ddd..f82774601 100644 --- a/src/ImageSharp/Common/Helpers/Buffer2DUtils.cs +++ b/src/ImageSharp/Common/Helpers/Buffer2DUtils.cs @@ -52,7 +52,7 @@ namespace SixLabors.ImageSharp { int offsetY = (row + i - radiusY).Clamp(minRow, maxRow); int offsetX = sourceOffsetColumnBase.Clamp(minColumn, maxColumn); - Span sourceRowSpan = sourcePixels.GetRowSpanUnchecked(offsetY); + Span sourceRowSpan = sourcePixels.GetRowSpan(offsetY); var currentColor = sourceRowSpan[offsetX].ToVector4(); vector.Sum(Unsafe.Add(ref baseRef, i) * currentColor); @@ -94,7 +94,7 @@ namespace SixLabors.ImageSharp int sourceOffsetColumnBase = column + minColumn; int offsetY = row.Clamp(minRow, maxRow); - ref ComplexVector4 sourceRef = ref MemoryMarshal.GetReference(sourceValues.GetRowSpanUnchecked(offsetY)); + ref ComplexVector4 sourceRef = ref MemoryMarshal.GetReference(sourceValues.GetRowSpan(offsetY)); ref Complex64 baseRef = ref MemoryMarshal.GetReference(kernel); for (int x = 0; x < kernelLength; x++) diff --git a/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs b/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs index baab397f8..ff6e3a4ec 100644 --- a/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs +++ b/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs @@ -134,7 +134,7 @@ namespace SixLabors.ImageSharp for (int y = 0; y < matrixHeight; y++) { int offsetY = (row + y - radiusY).Clamp(minRow, maxRow); - Span sourceRowSpan = sourcePixels.GetRowSpanUnchecked(offsetY); + Span sourceRowSpan = sourcePixels.GetRowSpan(offsetY); for (int x = 0; x < matrixWidth; x++) { @@ -264,7 +264,7 @@ namespace SixLabors.ImageSharp for (int y = 0; y < matrixHeight; y++) { int offsetY = (row + y - radiusY).Clamp(minRow, maxRow); - Span sourceRowSpan = sourcePixels.GetRowSpanUnchecked(offsetY); + Span sourceRowSpan = sourcePixels.GetRowSpan(offsetY); for (int x = 0; x < matrixWidth; x++) { diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs index fdd371f54..80b20c025 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs @@ -320,7 +320,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.GetRowSpanUnchecked(newY); + Span pixelRow = pixels.GetRowSpan(newY); bool rowHasUndefinedPixels = rowsWithUndefinedPixelsSpan[y]; if (rowHasUndefinedPixels) @@ -391,7 +391,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp for (int y = 0; y < height; y++) { int newY = Invert(y, height, inverted); - Span pixelRow = pixels.GetRowSpanUnchecked(newY); + Span pixelRow = pixels.GetRowSpan(newY); bool rowHasUndefinedPixels = rowsWithUndefinedPixelsSpan[y]; if (rowHasUndefinedPixels) { @@ -841,7 +841,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp int newY = Invert(y, height, inverted); this.stream.Read(row.Array, 0, row.Length()); int offset = 0; - Span pixelRow = pixels.GetRowSpanUnchecked(newY); + Span pixelRow = pixels.GetRowSpan(newY); for (int x = 0; x < arrayWidth; x++) { @@ -893,7 +893,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp { this.stream.Read(buffer.Array, 0, stride); int newY = Invert(y, height, inverted); - Span pixelRow = pixels.GetRowSpanUnchecked(newY); + Span pixelRow = pixels.GetRowSpan(newY); int offset = 0; for (int x = 0; x < width; x++) @@ -949,7 +949,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp { this.stream.Read(row); int newY = Invert(y, height, inverted); - Span pixelSpan = pixels.GetRowSpanUnchecked(newY); + Span pixelSpan = pixels.GetRowSpan(newY); PixelOperations.Instance.FromBgr24Bytes( this.configuration, row.GetSpan(), @@ -978,7 +978,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp { this.stream.Read(row); int newY = Invert(y, height, inverted); - Span pixelSpan = pixels.GetRowSpanUnchecked(newY); + Span pixelSpan = pixels.GetRowSpan(newY); PixelOperations.Instance.FromBgra32Bytes( this.configuration, row.GetSpan(), @@ -1050,7 +1050,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp this.stream.Read(row); int newY = Invert(y, height, inverted); - Span pixelSpan = pixels.GetRowSpanUnchecked(newY); + Span pixelSpan = pixels.GetRowSpan(newY); PixelOperations.Instance.FromBgra32Bytes( this.configuration, @@ -1073,7 +1073,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp width); int newY = Invert(y, height, inverted); - Span pixelSpan = pixels.GetRowSpanUnchecked(newY); + Span pixelSpan = pixels.GetRowSpan(newY); for (int x = 0; x < width; x++) { @@ -1128,7 +1128,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp { this.stream.Read(buffer.Array, 0, stride); int newY = Invert(y, height, inverted); - Span pixelRow = pixels.GetRowSpanUnchecked(newY); + Span pixelRow = pixels.GetRowSpan(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 12056fb0c..1c7c606ca 100644 --- a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs @@ -240,7 +240,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp { for (int y = pixels.Height - 1; y >= 0; y--) { - Span pixelSpan = pixels.GetRowSpanUnchecked(y); + Span pixelSpan = pixels.GetRowSpan(y); PixelOperations.Instance.ToBgra32Bytes( this.configuration, pixelSpan, @@ -264,7 +264,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp { for (int y = pixels.Height - 1; y >= 0; y--) { - Span pixelSpan = pixels.GetRowSpanUnchecked(y); + Span pixelSpan = pixels.GetRowSpan(y); PixelOperations.Instance.ToBgr24Bytes( this.configuration, pixelSpan, @@ -288,7 +288,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp { for (int y = pixels.Height - 1; y >= 0; y--) { - Span pixelSpan = pixels.GetRowSpanUnchecked(y); + Span pixelSpan = pixels.GetRowSpan(y); PixelOperations.Instance.ToBgra5551Bytes( this.configuration, diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.cs index 87f5233e9..61e359869 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.cs @@ -136,20 +136,20 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { this.ComponentCount = componentBuffers.Count; - this.Component0 = componentBuffers[0].GetRowSpanUnchecked(row); + this.Component0 = componentBuffers[0].GetRowSpan(row); this.Component1 = Span.Empty; this.Component2 = Span.Empty; this.Component3 = Span.Empty; if (this.ComponentCount > 1) { - this.Component1 = componentBuffers[1].GetRowSpanUnchecked(row); + this.Component1 = componentBuffers[1].GetRowSpan(row); if (this.ComponentCount > 2) { - this.Component2 = componentBuffers[2].GetRowSpanUnchecked(row); + this.Component2 = componentBuffers[2].GetRowSpan(row); if (this.ComponentCount > 3) { - this.Component3 = componentBuffers[3].GetRowSpanUnchecked(row); + this.Component3 = componentBuffers[3].GetRowSpan(row); } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs index 294d1da19..fbb2b5272 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs @@ -167,7 +167,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder for (int y = 0; y < v; y++) { int blockRow = (mcuRow * v) + y; - Span blockSpan = component.SpectralBlocks.GetRowSpanUnchecked(blockRow); + Span blockSpan = component.SpectralBlocks.GetRowSpan(blockRow); ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan); for (int x = 0; x < h; x++) @@ -211,7 +211,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder for (int j = 0; j < h; j++) { - Span blockSpan = component.SpectralBlocks.GetRowSpanUnchecked(j); + Span blockSpan = component.SpectralBlocks.GetRowSpan(j); ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan); for (int i = 0; i < w; i++) @@ -334,7 +334,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder for (int y = 0; y < v; y++) { int blockRow = (mcuRow * v) + y; - Span blockSpan = component.SpectralBlocks.GetRowSpanUnchecked(blockRow); + Span blockSpan = component.SpectralBlocks.GetRowSpan(blockRow); ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan); for (int x = 0; x < h; x++) @@ -377,7 +377,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder for (int j = 0; j < h; j++) { - Span blockSpan = component.SpectralBlocks.GetRowSpanUnchecked(j); + Span blockSpan = component.SpectralBlocks.GetRowSpan(j); ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan); for (int i = 0; i < w; i++) @@ -403,7 +403,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder for (int j = 0; j < h; j++) { - Span blockSpan = component.SpectralBlocks.GetRowSpanUnchecked(j); + Span blockSpan = component.SpectralBlocks.GetRowSpan(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 5a52c3ac4..39c8be312 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs @@ -89,7 +89,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder int yBuffer = y * this.blockAreaSize.Height; - Span blockRow = this.Component.SpectralBlocks.GetRowSpanUnchecked(yBlock); + Span blockRow = this.Component.SpectralBlocks.GetRowSpan(yBlock); ref Block8x8 blockRowBase = ref MemoryMarshal.GetReference(blockRow); diff --git a/src/ImageSharp/Formats/Jpeg/Components/RowOctet.cs b/src/ImageSharp/Formats/Jpeg/Components/RowOctet.cs index 54976110b..57a134703 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/RowOctet.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/RowOctet.cs @@ -27,14 +27,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components { int y = startY; int height = buffer.Height; - this.row0 = y < height ? buffer.GetRowSpanUnchecked(y++) : default; - this.row1 = y < height ? buffer.GetRowSpanUnchecked(y++) : default; - this.row2 = y < height ? buffer.GetRowSpanUnchecked(y++) : default; - this.row3 = y < height ? buffer.GetRowSpanUnchecked(y++) : default; - this.row4 = y < height ? buffer.GetRowSpanUnchecked(y++) : default; - this.row5 = y < height ? buffer.GetRowSpanUnchecked(y++) : default; - this.row6 = y < height ? buffer.GetRowSpanUnchecked(y++) : default; - this.row7 = y < height ? buffer.GetRowSpanUnchecked(y) : default; + this.row0 = y < height ? buffer.GetRowSpan(y++) : default; + this.row1 = y < height ? buffer.GetRowSpan(y++) : default; + this.row2 = y < height ? buffer.GetRowSpan(y++) : default; + this.row3 = y < height ? buffer.GetRowSpan(y++) : default; + this.row4 = y < height ? buffer.GetRowSpan(y++) : default; + this.row5 = y < height ? buffer.GetRowSpan(y++) : default; + this.row6 = y < height ? buffer.GetRowSpan(y++) : default; + this.row7 = y < height ? buffer.GetRowSpan(y) : default; } public Span this[int y] diff --git a/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs b/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs index 6768f4db3..a86fd3bce 100644 --- a/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs +++ b/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs @@ -233,7 +233,7 @@ namespace SixLabors.ImageSharp.Formats.Tga { this.currentStream.Read(row); int newY = Invert(y, height, inverted); - Span pixelRow = pixels.GetRowSpanUnchecked(newY); + Span pixelRow = pixels.GetRowSpan(newY); switch (colorMapPixelSizeInBytes) { case 2: @@ -297,7 +297,7 @@ namespace SixLabors.ImageSharp.Formats.Tga for (int y = 0; y < height; y++) { int newY = Invert(y, height, inverted); - Span pixelRow = pixels.GetRowSpanUnchecked(newY); + Span pixelRow = pixels.GetRowSpan(newY); int rowStartIdx = y * width * bytesPerPixel; for (int x = 0; x < width; x++) { @@ -344,7 +344,7 @@ namespace SixLabors.ImageSharp.Formats.Tga { this.currentStream.Read(row); int newY = Invert(y, height, inverted); - Span pixelSpan = pixels.GetRowSpanUnchecked(newY); + Span pixelSpan = pixels.GetRowSpan(newY); PixelOperations.Instance.FromL8Bytes( this.configuration, row.GetSpan(), @@ -379,7 +379,7 @@ namespace SixLabors.ImageSharp.Formats.Tga } int newY = Invert(y, height, inverted); - Span pixelSpan = pixels.GetRowSpanUnchecked(newY); + Span pixelSpan = pixels.GetRowSpan(newY); PixelOperations.Instance.FromBgra5551Bytes( this.configuration, rowSpan, @@ -406,7 +406,7 @@ namespace SixLabors.ImageSharp.Formats.Tga { this.currentStream.Read(row); int newY = Invert(y, height, inverted); - Span pixelSpan = pixels.GetRowSpanUnchecked(newY); + Span pixelSpan = pixels.GetRowSpan(newY); PixelOperations.Instance.FromBgr24Bytes( this.configuration, row.GetSpan(), @@ -433,7 +433,7 @@ namespace SixLabors.ImageSharp.Formats.Tga { this.currentStream.Read(row); int newY = Invert(y, height, inverted); - Span pixelSpan = pixels.GetRowSpanUnchecked(newY); + Span pixelSpan = pixels.GetRowSpan(newY); PixelOperations.Instance.FromBgra32Bytes( this.configuration, row.GetSpan(), @@ -463,7 +463,7 @@ namespace SixLabors.ImageSharp.Formats.Tga for (int y = 0; y < height; y++) { int newY = Invert(y, height, inverted); - Span pixelRow = pixels.GetRowSpanUnchecked(newY); + Span pixelRow = pixels.GetRowSpan(newY); int rowStartIdx = y * width * bytesPerPixel; for (int x = 0; x < width; x++) { diff --git a/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs b/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs index 9ad5a047e..d1ec2ed4c 100644 --- a/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs +++ b/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs @@ -255,7 +255,7 @@ namespace SixLabors.ImageSharp.Formats.Tga { for (int y = pixels.Height - 1; y >= 0; y--) { - Span pixelSpan = pixels.GetRowSpanUnchecked(y); + Span pixelSpan = pixels.GetRowSpan(y); PixelOperations.Instance.ToL8Bytes( this.configuration, pixelSpan, @@ -279,7 +279,7 @@ namespace SixLabors.ImageSharp.Formats.Tga { for (int y = pixels.Height - 1; y >= 0; y--) { - Span pixelSpan = pixels.GetRowSpanUnchecked(y); + Span pixelSpan = pixels.GetRowSpan(y); PixelOperations.Instance.ToBgra5551Bytes( this.configuration, pixelSpan, @@ -303,7 +303,7 @@ namespace SixLabors.ImageSharp.Formats.Tga { for (int y = pixels.Height - 1; y >= 0; y--) { - Span pixelSpan = pixels.GetRowSpanUnchecked(y); + Span pixelSpan = pixels.GetRowSpan(y); PixelOperations.Instance.ToBgr24Bytes( this.configuration, pixelSpan, @@ -327,7 +327,7 @@ namespace SixLabors.ImageSharp.Formats.Tga { for (int y = pixels.Height - 1; y >= 0; y--) { - Span pixelSpan = pixels.GetRowSpanUnchecked(y); + Span pixelSpan = pixels.GetRowSpan(y); PixelOperations.Instance.ToBgra32Bytes( this.configuration, pixelSpan, diff --git a/src/ImageSharp/ImageFrame{TPixel}.cs b/src/ImageSharp/ImageFrame{TPixel}.cs index 0d7681084..d5045a599 100644 --- a/src/ImageSharp/ImageFrame{TPixel}.cs +++ b/src/ImageSharp/ImageFrame{TPixel}.cs @@ -150,11 +150,19 @@ namespace SixLabors.ImageSharp /// The at the specified position. public TPixel this[int x, int y] { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this.PixelBuffer[x, y]; + [MethodImpl(InliningOptions.ShortMethod)] + get + { + this.VerifyCoords(x, y); + return this.PixelBuffer.GetElementUnsafe(x, y); + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - set => this.PixelBuffer[x, y] = value; + [MethodImpl(InliningOptions.ShortMethod)] + set + { + this.VerifyCoords(x, y); + this.PixelBuffer.GetElementUnsafe(x, y) = value; + } } /// @@ -287,6 +295,26 @@ namespace SixLabors.ImageSharp } } + [MethodImpl(InliningOptions.ShortMethod)] + private void VerifyCoords(int x, int y) + { + if (x < 0 || x >= this.Width) + { + ThrowArgumentOutOfRangeException(nameof(x)); + } + + if (y < 0 || y >= this.Height) + { + ThrowArgumentOutOfRangeException(nameof(y)); + } + } + + [MethodImpl(InliningOptions.ColdPath)] + private static void ThrowArgumentOutOfRangeException(string paramName) + { + throw new ArgumentOutOfRangeException(paramName); + } + /// /// A implementing the clone logic for . /// diff --git a/src/ImageSharp/Image{TPixel}.cs b/src/ImageSharp/Image{TPixel}.cs index c60f6638c..9199696af 100644 --- a/src/ImageSharp/Image{TPixel}.cs +++ b/src/ImageSharp/Image{TPixel}.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Memory; @@ -146,8 +147,19 @@ namespace SixLabors.ImageSharp /// The at the specified position. public TPixel this[int x, int y] { - get => this.PixelSource.PixelBuffer[x, y]; - set => this.PixelSource.PixelBuffer[x, y] = value; + [MethodImpl(InliningOptions.ShortMethod)] + get + { + this.VerifyCoords(x, y); + return this.PixelSource.PixelBuffer.GetElementUnsafe(x, y); + } + + [MethodImpl(InliningOptions.ShortMethod)] + set + { + this.VerifyCoords(x, y); + this.PixelSource.PixelBuffer.GetElementUnsafe(x, y) = value; + } } /// @@ -265,5 +277,25 @@ namespace SixLabors.ImageSharp return rootSize; } + + [MethodImpl(InliningOptions.ShortMethod)] + private void VerifyCoords(int x, int y) + { + if (x < 0 || x >= this.Width) + { + ThrowArgumentOutOfRangeException(nameof(x)); + } + + if (y < 0 || y >= this.Height) + { + ThrowArgumentOutOfRangeException(nameof(y)); + } + } + + [MethodImpl(InliningOptions.ColdPath)] + private static void ThrowArgumentOutOfRangeException(string paramName) + { + throw new ArgumentOutOfRangeException(paramName); + } } } diff --git a/src/ImageSharp/Memory/Buffer2D{T}.cs b/src/ImageSharp/Memory/Buffer2D{T}.cs index ef9898ad3..fe9e28e2e 100644 --- a/src/ImageSharp/Memory/Buffer2D{T}.cs +++ b/src/ImageSharp/Memory/Buffer2D{T}.cs @@ -3,6 +3,7 @@ using System; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; namespace SixLabors.ImageSharp.Memory { @@ -68,29 +69,15 @@ namespace SixLabors.ImageSharp.Memory [MethodImpl(MethodImplOptions.AggressiveInlining)] get { + DebugGuard.MustBeGreaterThanOrEqualTo(x, 0, nameof(x)); + DebugGuard.MustBeGreaterThanOrEqualTo(y, 0, nameof(y)); DebugGuard.MustBeLessThan(x, this.Width, nameof(x)); DebugGuard.MustBeLessThan(y, this.Height, nameof(y)); - return ref this.GetRowSpanUnchecked(y)[x]; + return ref this.GetRowSpan(y)[x]; } } - /// - /// Gets a to the row 'y' beginning from the pixel at the first pixel on that row. - /// - /// The y (row) coordinate. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Span GetRowSpan(int y) - { - Guard.MustBeGreaterThanOrEqualTo(y, 0, nameof(y)); - Guard.MustBeLessThan(y, this.Height, nameof(y)); - - return this.cachedMemory.Length > 0 - ? this.cachedMemory.Span.Slice(y * this.Width, this.Width) - : this.GetRowMemorySlow(y).Span; - } - /// /// Disposes the instance /// @@ -101,10 +88,16 @@ namespace SixLabors.ImageSharp.Memory } /// - /// Same as , but does not validate index in Release mode. + /// Gets a to the row 'y' beginning from the pixel at the first pixel on that row. /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal Span GetRowSpanUnchecked(int y) + /// + /// This method does not validate the y argument for performance reason, + /// is being propagated from lower levels. + /// + /// The row index. + /// Thrown when row index is out of range. + [MethodImpl(InliningOptions.ShortMethod)] + public Span GetRowSpan(int y) { DebugGuard.MustBeGreaterThanOrEqualTo(y, 0, nameof(y)); DebugGuard.MustBeLessThan(y, this.Height, nameof(y)); @@ -114,6 +107,19 @@ namespace SixLabors.ImageSharp.Memory : this.GetRowMemorySlow(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 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 @@ -121,8 +127,8 @@ namespace SixLabors.ImageSharp.Memory /// /// The y (row) coordinate. /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal Memory GetRowMemoryFast(int y) + [MethodImpl(InliningOptions.ShortMethod)] + internal Memory GetFastRowMemory(int y) { DebugGuard.MustBeGreaterThanOrEqualTo(y, 0, nameof(y)); DebugGuard.MustBeLessThan(y, this.Height, nameof(y)); @@ -136,7 +142,8 @@ namespace SixLabors.ImageSharp.Memory /// /// The y (row) coordinate. /// The . - internal Memory GetRowMemorySafe(int y) + [MethodImpl(InliningOptions.ShortMethod)] + internal Memory GetSafeRowMemory(int y) { DebugGuard.MustBeGreaterThanOrEqualTo(y, 0, nameof(y)); DebugGuard.MustBeLessThan(y, this.Height, nameof(y)); @@ -156,6 +163,13 @@ namespace SixLabors.ImageSharp.Memory [MethodImpl(InliningOptions.ColdPath)] private Memory GetRowMemorySlow(int y) => this.MemoryGroup.GetBoundedSlice(y * this.Width, this.Width); + [MethodImpl(InliningOptions.ColdPath)] + private ref T GetElementSlow(int x, int y) + { + Span span = this.GetRowMemorySlow(y).Span; + return ref span[x]; + } + private static void SwapOwnData(Buffer2D a, Buffer2D b, bool swapCachedMemory) { Size aSize = a.Size(); diff --git a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs index 023a4cea0..1ebd6476e 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs @@ -364,7 +364,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution { for (int y = rows.Min; y < rows.Max; y++) { - Span targetRowSpan = this.targetValues.GetRowSpanUnchecked(y).Slice(this.bounds.X); + Span targetRowSpan = this.targetValues.GetRowSpan(y).Slice(this.bounds.X); for (int x = 0; x < this.bounds.Width; x++) { @@ -413,7 +413,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution { for (int y = rows.Min; y < rows.Max; y++) { - Span targetRowSpan = this.targetValues.GetRowSpanUnchecked(y).Slice(this.bounds.X); + Span targetRowSpan = this.targetValues.GetRowSpan(y).Slice(this.bounds.X); for (int x = 0; x < this.bounds.Width; x++) { @@ -452,7 +452,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution { for (int y = rows.Min; y < rows.Max; y++) { - Span targetRowSpan = this.targetPixels.GetRowSpanUnchecked(y).Slice(this.bounds.X); + Span targetRowSpan = this.targetPixels.GetRowSpan(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); @@ -504,8 +504,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution for (int y = rows.Min; y < rows.Max; y++) { - Span targetPixelSpan = this.targetPixels.GetRowSpanUnchecked(y).Slice(this.bounds.X); - Span sourceRowSpan = this.sourceValues.GetRowSpanUnchecked(y).Slice(this.bounds.X); + Span targetPixelSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X); + Span sourceRowSpan = this.sourceValues.GetRowSpan(y).Slice(this.bounds.X); ref Vector4 sourceRef = ref MemoryMarshal.GetReference(sourceRowSpan); for (int x = 0; x < this.bounds.Width; x++) diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs index 9d68196f5..1c4987c79 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs @@ -119,7 +119,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution for (int y = rows.Min; y < rows.Max; y++) { - Span targetRowSpan = this.targetPixels.GetRowSpanUnchecked(y).Slice(this.bounds.X); + Span targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X); PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, span.Length), span); if (this.preserveAlpha) diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs index 809cda8a3..33a8ab7d1 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs @@ -118,7 +118,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution for (int y = rows.Min; y < rows.Max; y++) { - Span targetRowSpan = this.targetPixels.GetRowSpanUnchecked(y).Slice(this.bounds.X); + Span targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X); PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, span.Length), span); if (this.preserveAlpha) diff --git a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs index 426a2800c..542ee389b 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs @@ -106,7 +106,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution for (int y = rows.Min; y < rows.Max; y++) { - Span targetRowSpan = this.targetPixels.GetRowSpanUnchecked(y).Slice(this.bounds.X); + Span targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X); PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, span.Length), span); if (this.preserveAlpha) diff --git a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs index 2764b0b3f..c4da1e4b0 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs @@ -114,8 +114,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution { for (int y = rows.Min; y < rows.Max; y++) { - ref TPixel passPixelsBase = ref MemoryMarshal.GetReference(this.passPixels.GetRowSpanUnchecked(y)); - ref TPixel targetPixelsBase = ref MemoryMarshal.GetReference(this.targetPixels.GetRowSpanUnchecked(y)); + ref TPixel passPixelsBase = ref MemoryMarshal.GetReference(this.passPixels.GetRowSpan(y)); + ref TPixel targetPixelsBase = ref MemoryMarshal.GetReference(this.targetPixels.GetRowSpan(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 9bf18671a..50c0a22d3 100644 --- a/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs @@ -177,7 +177,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects } } - Span targetRowAreaPixelSpan = this.targetPixels.GetRowSpanUnchecked(y).Slice(this.bounds.X, this.bounds.Width); + Span targetRowAreaPixelSpan = this.targetPixels.GetRowSpan(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 286eab8c0..eb666a4f1 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor{TPixel}.cs @@ -529,7 +529,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization } [MethodImpl(InliningOptions.ShortMethod)] - public Span GetCdfLutSpan(int tileX, int tileY) => this.cdfLutBuffer2D.GetRowSpanUnchecked(tileY).Slice(tileX * this.luminanceLevels, this.luminanceLevels); + public Span GetCdfLutSpan(int tileX, int tileY) => this.cdfLutBuffer2D.GetRowSpan(tileY).Slice(tileX * this.luminanceLevels, this.luminanceLevels); /// /// Remaps the grey value with the cdf. @@ -605,7 +605,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); - ref int cdfMinBase = ref MemoryMarshal.GetReference(this.cdfMinBuffer2D.GetRowSpanUnchecked(cdfY)); + ref int cdfMinBase = ref MemoryMarshal.GetReference(this.cdfMinBuffer2D.GetRowSpan(cdfY)); using IMemoryOwner histogramBuffer = this.allocator.Allocate(this.luminanceLevels); Span histogram = histogramBuffer.GetSpan(); @@ -614,7 +614,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization for (int x = 0; x < this.sourceWidth; x += this.tileWidth) { histogram.Clear(); - Span cdfLutSpan = this.cdfLutBuffer2D.GetRowSpanUnchecked(index).Slice(cdfX * this.luminanceLevels, this.luminanceLevels); + Span cdfLutSpan = this.cdfLutBuffer2D.GetRowSpan(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/Transforms/Resize/ResizeKernelMap.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs index 5ee32f85f..cc9516956 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs @@ -241,7 +241,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms $"Error in KernelMap.CreateKernel({dataRowIndex},{left},{right}): left > this.data.Width"); } - Span rowSpan = this.data.GetRowSpanUnchecked(dataRowIndex); + Span rowSpan = this.data.GetRowSpan(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 d40a28b5d..c3f865806 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs @@ -102,7 +102,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms [MethodImpl(MethodImplOptions.AggressiveInlining)] public Span GetColumnSpan(int x, int startY) { - return this.transposedFirstPassBuffer.GetRowSpanUnchecked(x).Slice(startY - this.currentWindow.Min); + return this.transposedFirstPassBuffer.GetRowSpan(x).Slice(startY - this.currentWindow.Min); } public void Initialize() @@ -138,7 +138,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms Unsafe.Add(ref tempRowBase, x) = kernel.ConvolveCore(ref firstPassColumnBase); } - Span targetRowSpan = destination.GetRowSpanUnchecked(y); + Span targetRowSpan = destination.GetRowSpan(y); PixelOperations.Instance.FromVector4Destructive(this.configuration, tempColSpan, targetRowSpan, this.conversionModifiers); } diff --git a/src/ImageSharp/Processing/Processors/Transforms/TransformKernelMap.cs b/src/ImageSharp/Processing/Processors/Transforms/TransformKernelMap.cs index 7e261b81e..a0d44cb7a 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/TransformKernelMap.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/TransformKernelMap.cs @@ -53,7 +53,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// The reference to the first item of the window. [MethodImpl(InliningOptions.ShortMethod)] public ref float GetYStartReference(int y) - => ref MemoryMarshal.GetReference(this.yBuffer.GetRowSpanUnchecked(y)); + => ref MemoryMarshal.GetReference(this.yBuffer.GetRowSpan(y)); /// /// Gets a reference to the first item of the x window. @@ -61,7 +61,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// The reference to the first item of the window. [MethodImpl(InliningOptions.ShortMethod)] public ref float GetXStartReference(int y) - => ref MemoryMarshal.GetReference(this.xBuffer.GetRowSpanUnchecked(y)); + => ref MemoryMarshal.GetReference(this.xBuffer.GetRowSpan(y)); public void Convolve( Vector2 transformedPoint, diff --git a/tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsPixel.cs b/tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsPixel.cs index 68cfa96ed..4241a12f6 100644 --- a/tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsPixel.cs +++ b/tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsPixel.cs @@ -78,7 +78,7 @@ namespace SixLabors.ImageSharp.Benchmarks Buffer2D pixels = image.GetRootFramePixelBuffer(); for (int y = 0; y < image.Height; y++) { - Span span = pixels.GetRowSpanUnchecked(y); + Span span = pixels.GetRowSpan(y); this.BulkVectorConvert(span, span, span, amounts.GetSpan()); } @@ -98,7 +98,7 @@ namespace SixLabors.ImageSharp.Benchmarks Buffer2D pixels = image.GetRootFramePixelBuffer(); for (int y = 0; y < image.Height; y++) { - Span span = pixels.GetRowSpanUnchecked(y); + Span span = pixels.GetRowSpan(y); this.BulkPixelConvert(span, span, span, amounts.GetSpan()); } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.ComponentData.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.ComponentData.cs index d97c75dc6..b9526994e 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.ComponentData.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.ComponentData.cs @@ -65,7 +65,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils for (int y = 0; y < result.HeightInBlocks; y++) { - Span blockRow = c.SpectralBlocks.GetRowSpanUnchecked(y); + Span blockRow = c.SpectralBlocks.GetRowSpan(y); for (int x = 0; x < result.WidthInBlocks; x++) { short[] data = blockRow[x].ToArray(); diff --git a/tests/ImageSharp.Tests/Image/ImageFrameTests.cs b/tests/ImageSharp.Tests/Image/ImageFrameTests.cs new file mode 100644 index 000000000..58d7d7981 --- /dev/null +++ b/tests/ImageSharp.Tests/Image/ImageFrameTests.cs @@ -0,0 +1,96 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; +using Xunit; + +namespace SixLabors.ImageSharp.Tests +{ + public class ImageFrameTests + { + public class Indexer + { + private readonly Configuration configuration = Configuration.CreateDefaultInstance(); + + private void LimitBufferCapacity(int bufferCapacityInBytes) + { + var allocator = (ArrayPoolMemoryAllocator)this.configuration.MemoryAllocator; + allocator.BufferCapacityInBytes = bufferCapacityInBytes; + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void GetSet(bool enforceDisco) + { + if (enforceDisco) + { + this.LimitBufferCapacity(100); + } + + using var image = new Image(this.configuration, 10, 10); + ImageFrame frame = image.Frames.RootFrame; + Rgba32 val = frame[3, 4]; + Assert.Equal(default(Rgba32), val); + frame[3, 4] = Color.Red; + val = frame[3, 4]; + Assert.Equal(Color.Red.ToRgba32(), val); + } + + public static TheoryData OutOfRangeData = new TheoryData() + { + { false, -1 }, + { false, 10 }, + { true, -1 }, + { true, 10 }, + }; + + [Theory] + [MemberData(nameof(OutOfRangeData))] + public void Get_OutOfRangeX(bool enforceDisco, int x) + { + if (enforceDisco) + { + this.LimitBufferCapacity(100); + } + + using var image = new Image(this.configuration, 10, 10); + ImageFrame frame = image.Frames.RootFrame; + ArgumentOutOfRangeException ex = Assert.Throws(() => _ = frame[x, 3]); + Assert.Equal("x", ex.ParamName); + } + + [Theory] + [MemberData(nameof(OutOfRangeData))] + public void Set_OutOfRangeX(bool enforceDisco, int x) + { + if (enforceDisco) + { + this.LimitBufferCapacity(100); + } + + using var image = new Image(this.configuration, 10, 10); + ImageFrame frame = image.Frames.RootFrame; + ArgumentOutOfRangeException ex = Assert.Throws(() => frame[x, 3] = default); + Assert.Equal("x", ex.ParamName); + } + + [Theory] + [MemberData(nameof(OutOfRangeData))] + public void Set_OutOfRangeY(bool enforceDisco, int y) + { + if (enforceDisco) + { + this.LimitBufferCapacity(100); + } + + using var image = new Image(this.configuration, 10, 10); + ImageFrame frame = image.Frames.RootFrame; + ArgumentOutOfRangeException ex = Assert.Throws(() => frame[3, y] = default); + Assert.Equal("y", ex.ParamName); + } + } + } +} diff --git a/tests/ImageSharp.Tests/Image/ImageTests.cs b/tests/ImageSharp.Tests/Image/ImageTests.cs index 0fa917972..c99b75733 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.cs @@ -1,7 +1,9 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System; using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.Memory; @@ -85,5 +87,84 @@ namespace SixLabors.ImageSharp.Tests } } } + + public class Indexer + { + private readonly Configuration configuration = Configuration.CreateDefaultInstance(); + + private void LimitBufferCapacity(int bufferCapacityInBytes) + { + var allocator = (ArrayPoolMemoryAllocator)this.configuration.MemoryAllocator; + allocator.BufferCapacityInBytes = bufferCapacityInBytes; + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void GetSet(bool enforceDisco) + { + if (enforceDisco) + { + this.LimitBufferCapacity(100); + } + + using var image = new Image(this.configuration, 10, 10); + Rgba32 val = image[3, 4]; + Assert.Equal(default(Rgba32), val); + image[3, 4] = Color.Red; + val = image[3, 4]; + Assert.Equal(Color.Red.ToRgba32(), val); + } + + public static TheoryData OutOfRangeData = new TheoryData() + { + { false, -1 }, + { false, 10 }, + { true, -1 }, + { true, 10 }, + }; + + [Theory] + [MemberData(nameof(OutOfRangeData))] + public void Get_OutOfRangeX(bool enforceDisco, int x) + { + if (enforceDisco) + { + this.LimitBufferCapacity(100); + } + + using var image = new Image(this.configuration, 10, 10); + ArgumentOutOfRangeException ex = Assert.Throws(() => _ = image[x, 3]); + Assert.Equal("x", ex.ParamName); + } + + [Theory] + [MemberData(nameof(OutOfRangeData))] + public void Set_OutOfRangeX(bool enforceDisco, int x) + { + if (enforceDisco) + { + this.LimitBufferCapacity(100); + } + + using var image = new Image(this.configuration, 10, 10); + ArgumentOutOfRangeException ex = Assert.Throws(() => image[x, 3] = default); + Assert.Equal("x", ex.ParamName); + } + + [Theory] + [MemberData(nameof(OutOfRangeData))] + public void Set_OutOfRangeY(bool enforceDisco, int y) + { + if (enforceDisco) + { + this.LimitBufferCapacity(100); + } + + using var image = new Image(this.configuration, 10, 10); + ArgumentOutOfRangeException ex = Assert.Throws(() => image[3, y] = default); + Assert.Equal("y", ex.ParamName); + } + } } } diff --git a/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs b/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs index 36adf9856..69de85044 100644 --- a/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs +++ b/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs @@ -3,6 +3,7 @@ using System; using System.Buffers; +using System.Diagnostics; using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -95,7 +96,7 @@ namespace SixLabors.ImageSharp.Tests.Memory using (Buffer2D buffer = this.MemoryAllocator.Allocate2D(width, height)) { - Span span = buffer.GetRowSpanUnchecked(y); + Span span = buffer.GetRowSpan(y); Assert.Equal(width, span.Length); @@ -104,6 +105,48 @@ namespace SixLabors.ImageSharp.Tests.Memory } } + public static TheoryData GetRowSpanY_OutOfRange_Data = new TheoryData() + { + { Big, 10, 8, -1 }, + { Big, 10, 8, 8 }, + { 20, 10, 8, -1 }, + { 20, 10, 8, 10 }, + }; + + [Theory] + [MemberData(nameof(GetRowSpanY_OutOfRange_Data))] + public void GetRowSpan_OutOfRange(int bufferCapacity, int width, int height, int y) + { + this.MemoryAllocator.BufferCapacityInBytes = bufferCapacity; + using Buffer2D buffer = this.MemoryAllocator.Allocate2D(width, height); + + Exception ex = Assert.ThrowsAny(() => buffer.GetRowSpan(y)); + Assert.True(ex is ArgumentOutOfRangeException || ex is IndexOutOfRangeException); + } + + public static TheoryData Indexer_OutOfRange_Data = new TheoryData() + { + { Big, 10, 8, 1, -1 }, + { Big, 10, 8, 1, 8 }, + { Big, 10, 8, -1, 1 }, + { Big, 10, 8, 10, 1 }, + { 20, 10, 8, 1, -1 }, + { 20, 10, 8, 1, 10 }, + { 20, 10, 8, -1, 1 }, + { 20, 10, 8, 10, 1 }, + }; + + [Theory] + [MemberData(nameof(Indexer_OutOfRange_Data))] + public void Indexer_OutOfRange(int bufferCapacity, int width, int height, int x, int y) + { + this.MemoryAllocator.BufferCapacityInBytes = bufferCapacity; + using Buffer2D buffer = this.MemoryAllocator.Allocate2D(width, height); + + Exception ex = Assert.ThrowsAny(() => buffer[x, y]++); + Assert.True(ex is ArgumentOutOfRangeException || ex is IndexOutOfRangeException); + } + [Theory] [InlineData(Big, 42, 8, 0, 0)] [InlineData(Big, 400, 1000, 20, 10)] @@ -168,10 +211,10 @@ namespace SixLabors.ImageSharp.Tests.Memory Buffer2D.SwapOrCopyContent(dest, source); } - int actual1 = dest.GetRowSpanUnchecked(0)[0]; + int actual1 = dest.GetRowSpan(0)[0]; int actual2 = dest.GetRowSpan(0)[0]; - int actual3 = dest.GetRowMemorySafe(0).Span[0]; - int actual4 = dest.GetRowMemoryFast(0).Span[0]; + int actual3 = dest.GetSafeRowMemory(0).Span[0]; + int actual4 = dest.GetFastRowMemory(0).Span[0]; int actual5 = dest[0, 0]; Assert.Equal(1, actual1); @@ -199,7 +242,7 @@ namespace SixLabors.ImageSharp.Tests.Memory for (int y = 0; y < b.Height; y++) { - Span row = b.GetRowSpanUnchecked(y); + Span row = b.GetRowSpan(y); Span s = row.Slice(startIndex, columnCount); Span d = row.Slice(destIndex, columnCount); @@ -222,7 +265,7 @@ namespace SixLabors.ImageSharp.Tests.Memory for (int y = 0; y < b.Height; y++) { - Span row = b.GetRowSpanUnchecked(y); + Span row = b.GetRowSpan(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 eeddb7daa..77e899a4c 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.GetRowSpanUnchecked(y); + Span row = buffer.GetRowSpan(y); Assert.True(row.SequenceEqual(emptyRow)); } } @@ -151,7 +151,7 @@ namespace SixLabors.ImageSharp.Tests.Memory for (int y = area.Rectangle.Y; y < area.Rectangle.Bottom; y++) { - Span span = buffer.GetRowSpanUnchecked(y).Slice(area.Rectangle.X, area.Width); + Span span = buffer.GetRowSpan(y).Slice(area.Rectangle.X, area.Width); Assert.True(span.SequenceEqual(new int[area.Width])); } } From 4e213da07954b3a307adbc3aa462d1d5288c3991 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Thu, 13 Feb 2020 03:45:46 +0100 Subject: [PATCH 51/62] temporarily disable Calliphora+Disco to see effect on CI --- tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Baseline.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Baseline.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Baseline.cs index 99fd6b221..44408841c 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Baseline.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Baseline.cs @@ -15,7 +15,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg { [Theory] [WithFileCollection(nameof(BaselineTestJpegs), PixelTypes.Rgba32, false)] - [WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgba32, true)] + // [WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgba32, true)] public void DecodeBaselineJpeg(TestImageProvider provider, bool enforceDiscontiguousBuffers) where TPixel : struct, IPixel { From afe0c82bf69fc02bcc3f70c9f03299941436cfcc Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Thu, 13 Feb 2020 03:50:19 +0100 Subject: [PATCH 52/62] document ArgumentOutOfRangeException --- src/ImageSharp/ImageFrame{TPixel}.cs | 1 + src/ImageSharp/Image{TPixel}.cs | 1 + 2 files changed, 2 insertions(+) diff --git a/src/ImageSharp/ImageFrame{TPixel}.cs b/src/ImageSharp/ImageFrame{TPixel}.cs index d5045a599..03d37b2e7 100644 --- a/src/ImageSharp/ImageFrame{TPixel}.cs +++ b/src/ImageSharp/ImageFrame{TPixel}.cs @@ -148,6 +148,7 @@ namespace SixLabors.ImageSharp /// The x-coordinate of the pixel. Must be greater than or equal to zero and less than the width of the image. /// The y-coordinate of the pixel. Must be greater than or equal to zero and less than the height of the image. /// The at the specified position. + /// Thrown when the provided (x,y) coordinates are outside the image boundary. public TPixel this[int x, int y] { [MethodImpl(InliningOptions.ShortMethod)] diff --git a/src/ImageSharp/Image{TPixel}.cs b/src/ImageSharp/Image{TPixel}.cs index 9199696af..83be52dd6 100644 --- a/src/ImageSharp/Image{TPixel}.cs +++ b/src/ImageSharp/Image{TPixel}.cs @@ -145,6 +145,7 @@ namespace SixLabors.ImageSharp /// The x-coordinate of the pixel. Must be greater than or equal to zero and less than the width of the image. /// The y-coordinate of the pixel. Must be greater than or equal to zero and less than the height of the image. /// The at the specified position. + /// Thrown when the provided (x,y) coordinates are outside the image boundary. public TPixel this[int x, int y] { [MethodImpl(InliningOptions.ShortMethod)] From ee93aa406d6b1fd4aa253e72cc8d67387b6f2deb Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Thu, 20 Feb 2020 00:15:29 +0100 Subject: [PATCH 53/62] InBytes->InBytesSqrt, InPixels->InPixelsSqrt --- src/ImageSharp/ImageSharp.csproj | 3 ++- .../Advanced/AdvancedImageExtensionsTests.cs | 6 +++--- .../Formats/Bmp/BmpDecoderTests.cs | 8 ++++---- .../Formats/Bmp/BmpEncoderTests.cs | 2 +- .../Formats/Gif/GifDecoderTests.cs | 4 ++-- .../Formats/Gif/GifEncoderTests.cs | 2 +- .../Formats/Jpg/JpegDecoderTests.Baseline.cs | 16 +++++++++------- .../Formats/Jpg/JpegDecoderTests.Progressive.cs | 2 +- .../Formats/Jpg/JpegDecoderTests.cs | 2 +- .../Formats/Jpg/JpegEncoderTests.cs | 2 +- .../Formats/Png/PngDecoderTests.cs | 4 ++-- .../Formats/Png/PngEncoderTests.cs | 2 +- .../Formats/Tga/TgaDecoderTests.cs | 4 ++-- .../Formats/Tga/TgaEncoderTests.cs | 2 +- tests/ImageSharp.Tests/ImageSharp.Tests.csproj | 3 ++- .../TestUtilities/TestImageExtensions.cs | 12 ++++++------ 16 files changed, 39 insertions(+), 35 deletions(-) diff --git a/src/ImageSharp/ImageSharp.csproj b/src/ImageSharp/ImageSharp.csproj index be0e9032b..0d803475a 100644 --- a/src/ImageSharp/ImageSharp.csproj +++ b/src/ImageSharp/ImageSharp.csproj @@ -10,7 +10,8 @@ $(packageversion) 0.0.1 - netcoreapp3.1;netcoreapp2.1;netstandard2.1;netstandard2.0;netstandard1.3;net472 + + netcoreapp3.1 true true diff --git a/tests/ImageSharp.Tests/Advanced/AdvancedImageExtensionsTests.cs b/tests/ImageSharp.Tests/Advanced/AdvancedImageExtensionsTests.cs index d40d6ec4c..f9a562b9d 100644 --- a/tests/ImageSharp.Tests/Advanced/AdvancedImageExtensionsTests.cs +++ b/tests/ImageSharp.Tests/Advanced/AdvancedImageExtensionsTests.cs @@ -25,7 +25,7 @@ namespace SixLabors.ImageSharp.Tests.Advanced public void OwnedMemory_PixelDataIsCorrect(TestImageProvider provider) where TPixel : struct, IPixel { - provider.LimitAllocatorBufferCapacity().InPixels(200); + provider.LimitAllocatorBufferCapacity().InPixelsSqrt(200); using Image image = provider.GetImage(); @@ -106,7 +106,7 @@ namespace SixLabors.ImageSharp.Tests.Advanced public void GetPixelRowMemory_PixelDataIsCorrect(TestImageProvider provider) where TPixel : struct, IPixel { - provider.LimitAllocatorBufferCapacity().InPixels(200); + provider.LimitAllocatorBufferCapacity().InPixelsSqrt(200); using Image image = provider.GetImage(); @@ -147,7 +147,7 @@ namespace SixLabors.ImageSharp.Tests.Advanced public void GetPixelRowSpan_ShouldReferenceSpanOfMemory(TestImageProvider provider) where TPixel : struct, IPixel { - provider.LimitAllocatorBufferCapacity().InPixels(200); + provider.LimitAllocatorBufferCapacity().InPixelsSqrt(200); using Image image = provider.GetImage(); diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs index 264516063..4705fa3f2 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs @@ -50,7 +50,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp if (!string.IsNullOrEmpty(nonContiguousBuffersStr)) { - provider.LimitAllocatorBufferCapacity().InPixels(100); + provider.LimitAllocatorBufferCapacity().InPixelsSqrt(100); } using Image image = provider.GetImage(BmpDecoder); @@ -76,7 +76,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp public void BmpDecoder_DegenerateMemoryRequest_ShouldTranslateTo_ImageFormatException(TestImageProvider provider) where TPixel : struct, IPixel { - provider.LimitAllocatorBufferCapacity().InPixels(10); + provider.LimitAllocatorBufferCapacity().InPixelsSqrt(10); ImageFormatException ex = Assert.Throws(() => provider.GetImage(BmpDecoder)); Assert.IsType(ex.InnerException); } @@ -261,7 +261,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp { if (enforceDiscontiguousBuffers) { - provider.LimitAllocatorBufferCapacity().InBytes(400); + provider.LimitAllocatorBufferCapacity().InBytesSqrt(400); } using (Image image = provider.GetImage(new BmpDecoder { RleSkippedPixelHandling = RleSkippedPixelHandling.FirstColorOfPalette })) @@ -283,7 +283,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp { if (enforceNonContiguous) { - provider.LimitAllocatorBufferCapacity().InBytes(400); + provider.LimitAllocatorBufferCapacity().InBytesSqrt(400); } using (Image image = provider.GetImage(new BmpDecoder { RleSkippedPixelHandling = RleSkippedPixelHandling.Black })) diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs index 7f9736530..4fd1d6490 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs @@ -246,7 +246,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp public void Encode_WorksWithDiscontiguousBuffers(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) where TPixel : struct, IPixel { - provider.LimitAllocatorBufferCapacity().InBytes(100); + provider.LimitAllocatorBufferCapacity().InBytesSqrt(100); TestBmpEncoderCore(provider, bitsPerPixel); } diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs index 2b25f8e87..45c768892 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs @@ -175,7 +175,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif public void GifDecoder_DegenerateMemoryRequest_ShouldTranslateTo_ImageFormatException(TestImageProvider provider) where TPixel : struct, IPixel { - provider.LimitAllocatorBufferCapacity().InPixels(10); + provider.LimitAllocatorBufferCapacity().InPixelsSqrt(10); ImageFormatException ex = Assert.Throws(() => provider.GetImage(GifDecoder)); Assert.IsType(ex.InnerException); } @@ -190,7 +190,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif { TestImageProvider provider = BasicSerializer.Deserialize>(providerDump); - provider.LimitAllocatorBufferCapacity().InPixels(100); + provider.LimitAllocatorBufferCapacity().InPixelsSqrt(100); using Image image = provider.GetImage(GifDecoder); image.DebugSave(provider); diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs index 4c710cdb2..1fc99922d 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs @@ -33,7 +33,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif { if (limitAllocationBuffer) { - provider.LimitAllocatorBufferCapacity().InPixels(100); + provider.LimitAllocatorBufferCapacity().InPixelsSqrt(100); } using (Image image = provider.GetImage()) diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Baseline.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Baseline.cs index 44408841c..5428039c4 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Baseline.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Baseline.cs @@ -15,7 +15,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg { [Theory] [WithFileCollection(nameof(BaselineTestJpegs), PixelTypes.Rgba32, false)] - // [WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgba32, true)] + [WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgba32, true)] public void DecodeBaselineJpeg(TestImageProvider provider, bool enforceDiscontiguousBuffers) where TPixel : struct, IPixel { @@ -26,7 +26,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg if (!string.IsNullOrEmpty(nonContiguousBuffersStr)) { - provider.LimitAllocatorBufferCapacity().InBytes(200); + provider.LimitAllocatorBufferCapacity().InBytesSqrt(200); } using Image image = provider.GetImage(JpegDecoder); @@ -40,11 +40,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg } string providerDump = BasicSerializer.Serialize(provider); - RemoteExecutor.Invoke( - RunTest, - providerDump, - enforceDiscontiguousBuffers ? "Disco" : string.Empty) - .Dispose(); + RunTest(providerDump, enforceDiscontiguousBuffers ? "Disco" : string.Empty); + + // RemoteExecutor.Invoke( + // RunTest, + // providerDump, + // enforceDiscontiguousBuffers ? "Disco" : string.Empty) + // .Dispose(); } [Theory] diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Progressive.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Progressive.cs index 5d94ed985..c0169992d 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Progressive.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Progressive.cs @@ -26,7 +26,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg if (!string.IsNullOrEmpty(nonContiguousBuffersStr)) { - provider.LimitAllocatorBufferCapacity().InBytes(200); + provider.LimitAllocatorBufferCapacity().InBytesSqrt(200); } using Image image = provider.GetImage(JpegDecoder); diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs index 97dd4f001..32060df9a 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs @@ -111,7 +111,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg public void DegenerateMemoryRequest_ShouldTranslateTo_ImageFormatException(TestImageProvider provider) where TPixel : struct, IPixel { - provider.LimitAllocatorBufferCapacity().InBytes(10); + provider.LimitAllocatorBufferCapacity().InBytesSqrt(10); ImageFormatException ex = Assert.Throws(() => provider.GetImage(JpegDecoder)); this.Output.WriteLine(ex.Message); Assert.IsType(ex.InnerException); diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs index 56dd37874..b62a555b8 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs @@ -93,7 +93,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg ? ImageComparer.TolerantPercentage(0.1f) : ImageComparer.TolerantPercentage(5f); - provider.LimitAllocatorBufferCapacity().InBytes(200); + provider.LimitAllocatorBufferCapacity().InBytesSqrt(200); TestJpegEncoderCore(provider, subsample, 100, comparer); } diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs index 14b29d194..bf767e811 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs @@ -249,7 +249,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png public void PngDecoder_DegenerateMemoryRequest_ShouldTranslateTo_ImageFormatException(TestImageProvider provider) where TPixel : struct, IPixel { - provider.LimitAllocatorBufferCapacity().InPixels(10); + provider.LimitAllocatorBufferCapacity().InPixelsSqrt(10); ImageFormatException ex = Assert.Throws(() => provider.GetImage(PngDecoder)); Assert.IsType(ex.InnerException); } @@ -264,7 +264,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png { TestImageProvider provider = BasicSerializer.Deserialize>(providerDump); - provider.LimitAllocatorBufferCapacity().InPixels(100); + provider.LimitAllocatorBufferCapacity().InPixelsSqrt(100); using Image image = provider.GetImage(PngDecoder); image.DebugSave(provider, testOutputDetails: nonContiguousBuffersStr); diff --git a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs index a26bb7353..20a2d0233 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs @@ -410,7 +410,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png public void Encode_WorksWithDiscontiguousBuffers(TestImageProvider provider) where TPixel : struct, IPixel { - provider.LimitAllocatorBufferCapacity().InPixels(200); + provider.LimitAllocatorBufferCapacity().InPixelsSqrt(200); foreach (PngInterlaceMode interlaceMode in InterlaceMode) { TestPngEncoderCore( diff --git a/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs index a83ff5d63..985ccb596 100644 --- a/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs @@ -205,7 +205,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga public void TgaDecoder_DegenerateMemoryRequest_ShouldTranslateTo_ImageFormatException(TestImageProvider provider) where TPixel : struct, IPixel { - provider.LimitAllocatorBufferCapacity().InPixels(10); + provider.LimitAllocatorBufferCapacity().InPixelsSqrt(10); ImageFormatException ex = Assert.Throws(() => provider.GetImage(TgaDecoder)); Assert.IsType(ex.InnerException); } @@ -220,7 +220,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga { TestImageProvider provider = BasicSerializer.Deserialize>(providerDump); - provider.LimitAllocatorBufferCapacity().InPixels(100); + provider.LimitAllocatorBufferCapacity().InPixelsSqrt(100); using Image image = provider.GetImage(TgaDecoder); image.DebugSave(provider, testOutputDetails: nonContiguousBuffersStr); diff --git a/tests/ImageSharp.Tests/Formats/Tga/TgaEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Tga/TgaEncoderTests.cs index 2bb49a93e..339945f8b 100644 --- a/tests/ImageSharp.Tests/Formats/Tga/TgaEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tga/TgaEncoderTests.cs @@ -128,7 +128,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga public void Encode_WorksWithDiscontiguousBuffers(TestImageProvider provider, TgaBitsPerPixel bitsPerPixel) where TPixel : struct, IPixel { - provider.LimitAllocatorBufferCapacity().InPixels(100); + provider.LimitAllocatorBufferCapacity().InPixelsSqrt(100); TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.RunLength); } diff --git a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj index 34cdca49a..fdefa38e7 100644 --- a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj +++ b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj @@ -2,7 +2,8 @@ - netcoreapp3.1;netcoreapp2.1;net472 + + netcoreapp3.1 True True SixLabors.ImageSharp.Tests diff --git a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs index cea057330..d0836e19f 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs @@ -763,19 +763,19 @@ namespace SixLabors.ImageSharp.Tests } /// - /// Set the maximum buffer capacity to (areaDimensionBytes x areaDimensionBytes) bytes. + /// Set the maximum buffer capacity to bytesSqrt^2 bytes. /// - public void InBytes(int areaDimensionBytes) + public void InBytesSqrt(int bytesSqrt) { - this.allocator.BufferCapacityInBytes = areaDimensionBytes * areaDimensionBytes; + this.allocator.BufferCapacityInBytes = bytesSqrt * bytesSqrt; } /// - /// Set the maximum buffer capacity to (areaDimensionPixels x areaDimensionPixels x size of the pixel) bytes. + /// Set the maximum buffer capacity to pixelsSqrt^2 x sizeof(TPixel) bytes. /// - public void InPixels(int areaDimensionPixels) + public void InPixelsSqrt(int pixelsSqrt) { - this.allocator.BufferCapacityInBytes = areaDimensionPixels * areaDimensionPixels * this.pixelSizeInBytes; + this.allocator.BufferCapacityInBytes = pixelsSqrt * pixelsSqrt * this.pixelSizeInBytes; } } } From cf4d5a745753b36b50e867f9819bcccfa03079ac Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Thu, 20 Feb 2020 01:37:15 +0100 Subject: [PATCH 54/62] fix JpegDecoder --- .../Jpeg/Components/Block8x8F.CopyTo.cs | 2 +- .../Decoder/JpegComponentPostProcessor.cs | 10 +++++--- src/ImageSharp/Memory/Buffer2DExtensions.cs | 9 ------- src/ImageSharp/Memory/Buffer2D{T}.cs | 3 ++- .../Memory/MemoryAllocatorExtensions.cs | 20 +++++++++++++-- .../Formats/Jpg/JpegDecoderTests.Baseline.cs | 3 ++- .../Formats/Jpg/JpegDecoderTests.Images.cs | 8 ++++-- .../Helpers/RowIntervalTests.cs | 25 ------------------- .../ImageSharp.Tests/Memory/Buffer2DTests.cs | 13 ++++++++++ tests/ImageSharp.Tests/TestImages.cs | 4 +-- .../TestUtilities/TestImageExtensions.cs | 14 +++++------ 11 files changed, 56 insertions(+), 55 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.CopyTo.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.CopyTo.cs index 6bf9c8483..64d1d68b7 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.CopyTo.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.CopyTo.cs @@ -139,4 +139,4 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components } } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs index 39c8be312..b84d65271 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs @@ -31,12 +31,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { this.Component = component; this.ImagePostProcessor = imagePostProcessor; - this.ColorBuffer = memoryAllocator.Allocate2D( + this.blockAreaSize = this.Component.SubSamplingDivisors * 8; + this.ColorBuffer = memoryAllocator.Allocate2DOveraligned( imagePostProcessor.PostProcessorBufferSize.Width, - imagePostProcessor.PostProcessorBufferSize.Height); + imagePostProcessor.PostProcessorBufferSize.Height, + this.blockAreaSize.Height); this.BlockRowsPerStep = JpegImagePostProcessor.BlockRowsPerStep / this.Component.SubSamplingDivisors.Height; - this.blockAreaSize = this.Component.SubSamplingDivisors * 8; + } /// @@ -111,4 +113,4 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder this.currentComponentRowInBlocks += this.BlockRowsPerStep; } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Memory/Buffer2DExtensions.cs b/src/ImageSharp/Memory/Buffer2DExtensions.cs index 361132a82..297d49816 100644 --- a/src/ImageSharp/Memory/Buffer2DExtensions.cs +++ b/src/ImageSharp/Memory/Buffer2DExtensions.cs @@ -156,15 +156,6 @@ namespace SixLabors.ImageSharp.Memory where T : struct => new BufferArea(buffer); - /// - /// Gets a span for all the pixels in defined by - /// - internal static Span GetMultiRowSpan(this Buffer2D buffer, in RowInterval rows) - where T : struct - { - return buffer.GetSingleSpan().Slice(rows.Min * buffer.Width, rows.Height * buffer.Width); - } - /// /// Returns the size of the buffer. /// diff --git a/src/ImageSharp/Memory/Buffer2D{T}.cs b/src/ImageSharp/Memory/Buffer2D{T}.cs index fe9e28e2e..831d40641 100644 --- a/src/ImageSharp/Memory/Buffer2D{T}.cs +++ b/src/ImageSharp/Memory/Buffer2D{T}.cs @@ -95,6 +95,7 @@ namespace SixLabors.ImageSharp.Memory /// is being propagated from lower levels. /// /// The row index. + /// The of the pixels in the row. /// Thrown when row index is out of range. [MethodImpl(InliningOptions.ShortMethod)] public Span GetRowSpan(int y) @@ -117,7 +118,7 @@ namespace SixLabors.ImageSharp.Memory return ref Unsafe.Add(ref start, (y * this.Width) + x); } - return ref GetElementSlow(x, y); + return ref this.GetElementSlow(x, y); } /// diff --git a/src/ImageSharp/Memory/MemoryAllocatorExtensions.cs b/src/ImageSharp/Memory/MemoryAllocatorExtensions.cs index 1f1ded9a0..22d1bddd2 100644 --- a/src/ImageSharp/Memory/MemoryAllocatorExtensions.cs +++ b/src/ImageSharp/Memory/MemoryAllocatorExtensions.cs @@ -28,8 +28,8 @@ namespace SixLabors.ImageSharp.Memory where T : struct { long groupLength = (long)width * height; - MemoryGroup memorySource = memoryAllocator.AllocateGroup(groupLength, width, options); - return new Buffer2D(memorySource, width, height); + MemoryGroup memoryGroup = memoryAllocator.AllocateGroup(groupLength, width, options); + return new Buffer2D(memoryGroup, width, height); } /// @@ -48,6 +48,22 @@ namespace SixLabors.ImageSharp.Memory where T : struct => Allocate2D(memoryAllocator, size.Width, size.Height, options); + internal static Buffer2D Allocate2DOveraligned( + this MemoryAllocator memoryAllocator, + int width, + int height, + int alignmentMultiplier, + AllocationOptions options = AllocationOptions.None) + where T : struct + { + long groupLength = (long)width * height; + MemoryGroup memoryGroup = memoryAllocator.AllocateGroup( + groupLength, + width * alignmentMultiplier, + options); + return new Buffer2D(memoryGroup, width, height); + } + /// /// Allocates padded buffers for BMP encoder/decoder. (Replacing old PixelRow/PixelArea). /// diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Baseline.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Baseline.cs index 5428039c4..6ea61892c 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Baseline.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Baseline.cs @@ -16,6 +16,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [Theory] [WithFileCollection(nameof(BaselineTestJpegs), PixelTypes.Rgba32, false)] [WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgba32, true)] + [WithFile(TestImages.Jpeg.Baseline.Turtle420, PixelTypes.Rgba32, true)] public void DecodeBaselineJpeg(TestImageProvider provider, bool enforceDiscontiguousBuffers) where TPixel : struct, IPixel { @@ -26,7 +27,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg if (!string.IsNullOrEmpty(nonContiguousBuffersStr)) { - provider.LimitAllocatorBufferCapacity().InBytesSqrt(200); + provider.LimitAllocatorBufferCapacity().InPixels(1000 * 8); } using Image image = provider.GetImage(JpegDecoder); diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs index b7d7e6b83..a01f4d46c 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs @@ -13,10 +13,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg TestImages.Jpeg.Baseline.Cmyk, TestImages.Jpeg.Baseline.Ycck, TestImages.Jpeg.Baseline.Jpeg400, + TestImages.Jpeg.Baseline.Turtle420, TestImages.Jpeg.Baseline.Testorig420, // BUG: The following image has a high difference compared to the expected output: 1.0096% - // TestImages.Jpeg.Baseline.Jpeg420Small, + TestImages.Jpeg.Baseline.Jpeg420Small, TestImages.Jpeg.Issues.Fuzz.AccessViolationException922, TestImages.Jpeg.Baseline.Jpeg444, TestImages.Jpeg.Baseline.Bad.BadEOF, @@ -95,9 +96,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg // Baseline: [TestImages.Jpeg.Baseline.Calliphora] = 0.00002f / 100, [TestImages.Jpeg.Baseline.Bad.BadEOF] = 0.38f / 100, - [TestImages.Jpeg.Baseline.Testorig420] = 0.38f / 100, [TestImages.Jpeg.Baseline.Bad.BadRST] = 0.0589f / 100, + [TestImages.Jpeg.Baseline.Testorig420] = 0.38f / 100, + [TestImages.Jpeg.Baseline.Jpeg420Small] = 1.1f / 100, + [TestImages.Jpeg.Baseline.Turtle420] = 1.0f / 100, + // Progressive: [TestImages.Jpeg.Issues.MissingFF00ProgressiveGirl159] = 0.34f / 100, [TestImages.Jpeg.Issues.BadCoeffsProgressive178] = 0.38f / 100, diff --git a/tests/ImageSharp.Tests/Helpers/RowIntervalTests.cs b/tests/ImageSharp.Tests/Helpers/RowIntervalTests.cs index 222770195..fd1eb546b 100644 --- a/tests/ImageSharp.Tests/Helpers/RowIntervalTests.cs +++ b/tests/ImageSharp.Tests/Helpers/RowIntervalTests.cs @@ -10,31 +10,6 @@ namespace SixLabors.ImageSharp.Tests.Helpers { public class RowIntervalTests { - [Theory] - [InlineData(10, 20, 5, 10)] - [InlineData(1, 10, 0, 10)] - [InlineData(1, 10, 5, 8)] - [InlineData(1, 1, 0, 1)] - [InlineData(10, 20, 9, 10)] - [InlineData(10, 20, 0, 1)] - public void GetMultiRowSpan(int width, int height, int min, int max) - { - using (Buffer2D buffer = Configuration.Default.MemoryAllocator.Allocate2D(width, height)) - { - var rows = new RowInterval(min, max); - - Span span = buffer.GetMultiRowSpan(rows); - - ref int expected0 = ref buffer.GetSingleSpan()[min * width]; - int expectedLength = (max - min) * width; - - ref int actual0 = ref span[0]; - - Assert.Equal(span.Length, expectedLength); - Assert.True(Unsafe.AreSame(ref expected0, ref actual0)); - } - } - [Fact] public void Slice1() { diff --git a/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs b/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs index 69de85044..ea3419741 100644 --- a/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs +++ b/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs @@ -69,6 +69,19 @@ namespace SixLabors.ImageSharp.Tests.Memory } } + [Theory] + [InlineData(50, 10, 20, 4)] + public void Allocate2DOveraligned(int bufferCapacity, int width, int height, int alignmentMultiplier) + { + this.MemoryAllocator.BufferCapacityInBytes = sizeof(int) * bufferCapacity; + + using Buffer2D buffer = this.MemoryAllocator.Allocate2DOveraligned(width, height, alignmentMultiplier); + MemoryGroup memoryGroup = buffer.MemoryGroup; + int expectedAlignment = width * alignmentMultiplier; + + Assert.Equal(expectedAlignment, memoryGroup.BufferLength); + } + [Fact] public void CreateClean() { diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 16c570d63..ee919dc2f 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -142,7 +142,7 @@ namespace SixLabors.ImageSharp.Tests public const string Floorplan = "Jpg/baseline/Floorplan.jpg"; public const string Calliphora = "Jpg/baseline/Calliphora.jpg"; public const string Ycck = "Jpg/baseline/ycck.jpg"; - public const string Turtle = "Jpg/baseline/turtle.jpg"; + public const string Turtle420 = "Jpg/baseline/turtle.jpg"; public const string GammaDalaiLamaGray = "Jpg/baseline/gamma_dalai_lama_gray.jpg"; public const string Hiyamugi = "Jpg/baseline/Hiyamugi.jpg"; public const string Snake = "Jpg/baseline/Snake.jpg"; @@ -161,7 +161,7 @@ namespace SixLabors.ImageSharp.Tests public static readonly string[] All = { Cmyk, Ycck, Exif, Floorplan, - Calliphora, Turtle, GammaDalaiLamaGray, + Calliphora, Turtle420, GammaDalaiLamaGray, Hiyamugi, Jpeg400, Jpeg420Exif, Jpeg444, Ratio1x1, Testorig12bit, YcckSubsample1222 }; diff --git a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs index d0836e19f..4f89af70d 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs @@ -762,20 +762,18 @@ namespace SixLabors.ImageSharp.Tests this.pixelSizeInBytes = pixelSizeInBytes; } + public void InBytes(int totalBytes) => this.allocator.BufferCapacityInBytes = totalBytes; + + public void InPixels(int totalPixels) => this.InBytes(totalPixels * this.pixelSizeInBytes); + /// /// Set the maximum buffer capacity to bytesSqrt^2 bytes. /// - public void InBytesSqrt(int bytesSqrt) - { - this.allocator.BufferCapacityInBytes = bytesSqrt * bytesSqrt; - } + public void InBytesSqrt(int bytesSqrt) => this.InBytes(bytesSqrt * bytesSqrt); /// /// Set the maximum buffer capacity to pixelsSqrt^2 x sizeof(TPixel) bytes. /// - public void InPixelsSqrt(int pixelsSqrt) - { - this.allocator.BufferCapacityInBytes = pixelsSqrt * pixelsSqrt * this.pixelSizeInBytes; - } + public void InPixelsSqrt(int pixelsSqrt) => this.InPixels(pixelsSqrt * pixelsSqrt); } } From ccc00c31b63773e4240b259817e52ede88792255 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Fri, 21 Feb 2020 00:15:31 +0100 Subject: [PATCH 55/62] update reference images --- tests/Images/External | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Images/External b/tests/Images/External index 2d1505d70..f9b4bfe42 160000 --- a/tests/Images/External +++ b/tests/Images/External @@ -1 +1 @@ -Subproject commit 2d1505d7087d91cd83d0cda409aee213de7841ab +Subproject commit f9b4bfe42cacb3eefab02ada92ac771a9b93c080 From c5cb408d60b38c786437a76628240f54dae96104 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Fri, 21 Feb 2020 00:32:49 +0100 Subject: [PATCH 56/62] fix test execution --- .../Formats/Jpg/JpegDecoderTests.Progressive.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Progressive.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Progressive.cs index c0169992d..974f5b13b 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Progressive.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Progressive.cs @@ -40,10 +40,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg } string providerDump = BasicSerializer.Serialize(provider); + + // RunTest(providerDump, enforceDiscontiguousBuffers ? "Disco" : string.Empty); RemoteExecutor.Invoke( RunTest, providerDump, - enforceDiscontiguousBuffers ? "Disco" : string.Empty); + enforceDiscontiguousBuffers ? "Disco" : string.Empty) + .Dispose(); } } } From d7df00bc467c39ecf61c9fbbd5df9eb15fa941b1 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Fri, 21 Feb 2020 01:19:55 +0100 Subject: [PATCH 57/62] cover and harden ResizeProcessor --- src/ImageSharp/Configuration.cs | 3 +- .../Transforms/Resize/ResizeWorker.cs | 6 +++- .../Jpg/JpegDecoderTests.Progressive.cs | 1 - .../Processors/Dithering/DitherTests.cs | 2 +- .../Processors/Transforms/ResizeTests.cs | 33 +++++++++++++++++++ .../WithTestPatternImagesAttribute.cs | 6 ++-- 6 files changed, 44 insertions(+), 7 deletions(-) diff --git a/src/ImageSharp/Configuration.cs b/src/ImageSharp/Configuration.cs index 619be880a..47c7c54ea 100644 --- a/src/ImageSharp/Configuration.cs +++ b/src/ImageSharp/Configuration.cs @@ -108,7 +108,8 @@ namespace SixLabors.ImageSharp /// The default value is 1MB. /// /// - /// Currently only used by Resize. + /// Currently only used by Resize. If the working buffer is expected to be discontiguous, + /// min(WorkingBufferSizeHintInBytes, BufferCapacityInBytes) should be used. /// internal int WorkingBufferSizeHintInBytes { get; set; } = 1 * 1024 * 1024; diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs index c3f865806..cfb15a7da 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs @@ -74,10 +74,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms this.windowBandHeight = verticalKernelMap.MaxDiameter; + int workingBufferLimitHintInBytes = Math.Min( + configuration.WorkingBufferSizeHintInBytes, + configuration.MemoryAllocator.GetBufferCapacityInBytes()); + int numberOfWindowBands = ResizeHelper.CalculateResizeWorkerHeightInWindowBands( this.windowBandHeight, destWidth, - configuration.WorkingBufferSizeHintInBytes); + workingBufferLimitHintInBytes); this.workerHeight = Math.Min(this.sourceRectangle.Height, numberOfWindowBands * this.windowBandHeight); diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Progressive.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Progressive.cs index 974f5b13b..b8a791278 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Progressive.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Progressive.cs @@ -41,7 +41,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg string providerDump = BasicSerializer.Serialize(provider); - // RunTest(providerDump, enforceDiscontiguousBuffers ? "Disco" : string.Empty); RemoteExecutor.Invoke( RunTest, providerDump, diff --git a/tests/ImageSharp.Tests/Processing/Processors/Dithering/DitherTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Dithering/DitherTests.cs index 86f982118..0139f9a0d 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Dithering/DitherTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Dithering/DitherTests.cs @@ -32,7 +32,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization }; public static readonly TheoryData OrderedDitherers - = new TheoryData + = new TheoryData { { KnownDitherings.Bayer2x2, nameof(KnownDitherings.Bayer2x2) }, { KnownDitherings.Bayer4x4, nameof(KnownDitherings.Bayer4x4) }, diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs index fa2396251..2cbffef47 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs @@ -153,6 +153,39 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms } } + [Theory] + [WithTestPatternImages(100, 100, PixelTypes.Rgba32, 100, 100)] + [WithTestPatternImages(200, 200, PixelTypes.Rgba32, 31, 73)] + [WithTestPatternImages(200, 200, PixelTypes.Rgba32, 73, 31)] + [WithTestPatternImages(200, 193, PixelTypes.Rgba32, 13, 17)] + [WithTestPatternImages(200, 193, PixelTypes.Rgba32, 79, 23)] + [WithTestPatternImages(200, 503, PixelTypes.Rgba32, 61, 33)] + public void WorksWithDiscoBuffers( + TestImageProvider provider, + int workingBufferLimitInRows, + int bufferCapacityInRows) + where TPixel : struct, IPixel + { + using Image expected = provider.GetImage(); + int width = expected.Width; + Size destSize = expected.Size() / 4; + expected.Mutate(c => c.Resize(destSize, KnownResamplers.Bicubic, false)); + + // Replace configuration: + provider.Configuration = Configuration.CreateDefaultInstance(); + + // Note: when AllocatorCapacityInBytes < WorkingBufferSizeHintInBytes, + // ResizeProcessor is expected to use the minimum of the two values, when establishing the working buffer. + provider.LimitAllocatorBufferCapacity().InBytes(width * bufferCapacityInRows * SizeOfVector4); + provider.Configuration.WorkingBufferSizeHintInBytes = width * workingBufferLimitInRows * SizeOfVector4; + + using Image actual = provider.GetImage(); + actual.Mutate(c => c.Resize(destSize, KnownResamplers.Bicubic, false)); + actual.DebugSave(provider, $"{workingBufferLimitInRows}-{bufferCapacityInRows}"); + + ImageComparer.Exact.VerifySimilarity(expected, actual); + } + [Theory] [WithTestPatternImages(100, 100, DefaultPixelType)] public void Resize_Compand(TestImageProvider provider) diff --git a/tests/ImageSharp.Tests/TestUtilities/Attributes/WithTestPatternImagesAttribute.cs b/tests/ImageSharp.Tests/TestUtilities/Attributes/WithTestPatternImagesAttribute.cs index 7c659c64f..0f00f1d86 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Attributes/WithTestPatternImagesAttribute.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Attributes/WithTestPatternImagesAttribute.cs @@ -13,7 +13,7 @@ namespace SixLabors.ImageSharp.Tests public class WithTestPatternImagesAttribute : ImageDataAttributeBase { /// - /// Triggers passing an that produces a test pattern image of size width * height + /// Initializes a new instance of the class. /// /// The required width /// The required height @@ -25,7 +25,7 @@ namespace SixLabors.ImageSharp.Tests } /// - /// Triggers passing an that produces a test pattern image of size width * height + /// Initializes a new instance of the class. /// /// The member data to apply to theories /// The required width @@ -53,4 +53,4 @@ namespace SixLabors.ImageSharp.Tests protected override object[] GetFactoryMethodArgs(MethodInfo testMethod, Type factoryType) => new object[] { this.Width, this.Height }; } -} \ No newline at end of file +} From 0e91efeb09939ab2f961ae1e505851922907c112 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Fri, 21 Feb 2020 02:14:42 +0100 Subject: [PATCH 58/62] test common processors for disco buffers --- src/ImageSharp/ImageFrame{TPixel}.cs | 2 +- .../Transforms/Resize/ResizeWorker.cs | 3 ++ .../Processors/Convolution/BokehBlurTest.cs | 8 +++++ .../Processors/Convolution/DetectEdgesTest.cs | 11 ++++++ .../Processors/Dithering/DitherTests.cs | 21 +++++++++++ .../Processors/Filters/FilterTest.cs | 10 ++++++ .../Processors/Overlays/OverlayTestBase.cs | 8 +++++ .../Transforms/AffineTransformTests.cs | 13 +++++++ .../TestUtilities/TestUtils.cs | 36 +++++++++++++++++++ 9 files changed, 111 insertions(+), 1 deletion(-) rename tests/ImageSharp.Tests/Processing/{ => Processors}/Transforms/AffineTransformTests.cs (94%) diff --git a/src/ImageSharp/ImageFrame{TPixel}.cs b/src/ImageSharp/ImageFrame{TPixel}.cs index 03d37b2e7..a0bdf3f51 100644 --- a/src/ImageSharp/ImageFrame{TPixel}.cs +++ b/src/ImageSharp/ImageFrame{TPixel}.cs @@ -186,7 +186,7 @@ namespace SixLabors.ImageSharp throw new ArgumentException("ImageFrame.CopyTo(): target must be of the same size!", nameof(target)); } - this.GetPixelSpan().CopyTo(target.GetSingleSpan()); + this.PixelBuffer.MemoryGroup.CopyTo(target.MemoryGroup); } /// diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs index cfb15a7da..de339823e 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs @@ -74,6 +74,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms this.windowBandHeight = verticalKernelMap.MaxDiameter; + // We need to make sure the working buffer is contiguous: int workingBufferLimitHintInBytes = Math.Min( configuration.WorkingBufferSizeHintInBytes, configuration.MemoryAllocator.GetBufferCapacityInBytes()); @@ -117,6 +118,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms public void FillDestinationPixels(RowInterval rowInterval, Buffer2D destination) { Span tempColSpan = this.tempColumnBuffer.GetSpan(); + + // When creating transposedFirstPassBuffer, we made sure it's contiguous: Span transposedFirstPassBufferSpan = this.transposedFirstPassBuffer.GetSingleSpan(); for (int y = rowInterval.Min; y < rowInterval.Max; y++) diff --git a/tests/ImageSharp.Tests/Processing/Processors/Convolution/BokehBlurTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Convolution/BokehBlurTest.cs index fcd9eb3cd..2d5971124 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Convolution/BokehBlurTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Convolution/BokehBlurTest.cs @@ -196,5 +196,13 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution .Invoke(RunTest, BasicSerializer.Serialize(provider), BasicSerializer.Serialize(value)) .Dispose(); } + + [Theory] + [WithTestPatternImages(100, 300, PixelTypes.Bgr24)] + public void WorksWithDiscoBuffers(TestImageProvider provider) + where TPixel : struct, IPixel + { + provider.RunBufferCapacityLimitProcessorTest(41, c => c.BokehBlur()); + } } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Convolution/DetectEdgesTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Convolution/DetectEdgesTest.cs index a1f34856e..cfa733423 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Convolution/DetectEdgesTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Convolution/DetectEdgesTest.cs @@ -103,5 +103,16 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution image.CompareToReferenceOutput(ValidatorComparer, provider); } } + + [Theory] + [WithFile(Tests.TestImages.Png.Bike, nameof(DetectEdgesFilters), PixelTypes.Rgba32)] + public void WorksWithDiscoBuffers(TestImageProvider provider, EdgeDetectionOperators detector) + where TPixel : struct, IPixel + { + provider.RunBufferCapacityLimitProcessorTest( + 41, + c => c.DetectEdges(detector), + detector); + } } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Dithering/DitherTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Dithering/DitherTests.cs index 0139f9a0d..fa3e3637f 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Dithering/DitherTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Dithering/DitherTests.cs @@ -152,5 +152,26 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization comparer: ValidatorComparer, appendPixelTypeToFileName: false); } + + [Theory] + [WithFile(TestImages.Png.Bike, PixelTypes.Rgba32, nameof(OrderedDither.Ordered3x3))] + [WithFile(TestImages.Png.Bike, PixelTypes.Rgba32, nameof(ErrorDither.FloydSteinberg))] + public void CommonDitherers_WorkWithDiscoBuffers( + TestImageProvider provider, + string name) + where TPixel : struct, IPixel + { + IDither dither = TestUtils.GetDither(name); + if (SkipAllDitherTests) + { + return; + } + + provider.RunBufferCapacityLimitProcessorTest( + 41, + c => c.Dither(dither), + name, + ImageComparer.TolerantPercentage(0.001f)); + } } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Filters/FilterTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Filters/FilterTest.cs index a6d7ba451..9b5a5bfd2 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Filters/FilterTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/FilterTest.cs @@ -37,6 +37,16 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters provider.RunRectangleConstrainedValidatingProcessorTest((x, b) => x.Filter(m, b), comparer: ValidatorComparer); } + [Theory] + [WithTestPatternImages(70, 120, PixelTypes.Rgba32)] + public void FilterProcessor_WorksWithDiscoBuffers(TestImageProvider provider) + where TPixel : struct, IPixel + { + ColorMatrix m = CreateCombinedTestFilterMatrix(); + + provider.RunBufferCapacityLimitProcessorTest(37, c => c.Filter(m)); + } + private static ColorMatrix CreateCombinedTestFilterMatrix() { ColorMatrix brightness = KnownFilterMatrices.CreateBrightnessFilter(0.9F); diff --git a/tests/ImageSharp.Tests/Processing/Processors/Overlays/OverlayTestBase.cs b/tests/ImageSharp.Tests/Processing/Processors/Overlays/OverlayTestBase.cs index 0c09b6872..d959fdf67 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Overlays/OverlayTestBase.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Overlays/OverlayTestBase.cs @@ -54,6 +54,14 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Overlays provider.RunRectangleConstrainedValidatingProcessorTest(this.Apply); } + [Theory] + [WithTestPatternImages(70, 120, PixelTypes.Rgba32)] + public void WorksWithDiscoBuffers(TestImageProvider provider) + where TPixel : struct, IPixel + { + provider.RunBufferCapacityLimitProcessorTest(37, c => this.Apply(c, Color.DarkRed)); + } + protected abstract void Apply(IImageProcessingContext ctx, Color color); protected abstract void Apply(IImageProcessingContext ctx, float radiusX, float radiusY); diff --git a/tests/ImageSharp.Tests/Processing/Transforms/AffineTransformTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/AffineTransformTests.cs similarity index 94% rename from tests/ImageSharp.Tests/Processing/Transforms/AffineTransformTests.cs rename to tests/ImageSharp.Tests/Processing/Processors/Transforms/AffineTransformTests.cs index 399f1665f..9a4e7c618 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/AffineTransformTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/AffineTransformTests.cs @@ -213,6 +213,19 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms } } + [Theory] + [WithTestPatternImages(100, 100, PixelTypes.Rgba32, 21)] + public void WorksWithDiscoBuffers(TestImageProvider provider, int bufferCapacityInPixelRows) + where TPixel : struct, IPixel + { + AffineTransformBuilder builder = new AffineTransformBuilder() + .AppendRotationDegrees(50) + .AppendScale(new SizeF(.6F, .6F)); + provider.RunBufferCapacityLimitProcessorTest( + bufferCapacityInPixelRows, + c => c.Transform(builder)); + } + private static IResampler GetResampler(string name) { PropertyInfo property = typeof(KnownResamplers).GetTypeInfo().GetProperty(name); diff --git a/tests/ImageSharp.Tests/TestUtilities/TestUtils.cs b/tests/ImageSharp.Tests/TestUtilities/TestUtils.cs index 089e5805e..b5bfec17f 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestUtils.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestUtils.cs @@ -6,10 +6,12 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Reflection; +using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Processing.Processors.Dithering; using SixLabors.ImageSharp.Processing.Processors.Transforms; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; @@ -150,6 +152,28 @@ namespace SixLabors.ImageSharp.Tests where TPixel : struct, IPixel => GetColorByName(colorName).ToPixel(); + internal static void RunBufferCapacityLimitProcessorTest( + this TestImageProvider provider, + int bufferCapacityInPixelRows, + Action process, + object testOutputDetails = null, + ImageComparer comparer = null) + where TPixel : struct, IPixel + { + comparer??= ImageComparer.Exact; + using Image expected = provider.GetImage(); + int width = expected.Width; + expected.Mutate(process); + + var allocator = ArrayPoolMemoryAllocator.CreateDefault(); + provider.Configuration.MemoryAllocator = allocator; + allocator.BufferCapacityInBytes = bufferCapacityInPixelRows * width * Unsafe.SizeOf(); + + using Image actual = provider.GetImage(); + actual.Mutate(process); + comparer.VerifySimilarity(expected, actual); + } + /// /// Utility for testing image processor extension methods: /// 1. Run a processor defined by 'process' @@ -342,6 +366,18 @@ namespace SixLabors.ImageSharp.Tests return (IResampler)property.GetValue(null); } + public static IDither GetDither(string name) + { + PropertyInfo property = typeof(KnownDitherings).GetTypeInfo().GetProperty(name); + + if (property is null) + { + throw new Exception($"No dither named '{name}"); + } + + return (IDither)property.GetValue(null); + } + public static string[] GetAllResamplerNames(bool includeNearestNeighbour = true) { return typeof(KnownResamplers).GetProperties(BindingFlags.Public | BindingFlags.Static) From dabd82c8d29a6ff6c6e582477be5b3a8b5010da5 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Fri, 21 Feb 2020 02:32:58 +0100 Subject: [PATCH 59/62] fix remaining single-buffer specific code --- .../Decoder/JpegComponentPostProcessor.cs | 1 - src/ImageSharp/Image.LoadPixelData.cs | 7 ++++--- src/ImageSharp/ImageFrame.LoadPixelData.cs | 4 +++- src/ImageSharp/ImageFrame.cs | 3 ++- .../ImageFrameCollection{TPixel}.cs | 2 +- src/ImageSharp/ImageFrame{TPixel}.cs | 15 +++++++++++---- .../MemoryGroupExtensions.cs | 19 ++++++++++--------- .../Memory/TransformItemsDelegate{T}.cs | 2 +- 8 files changed, 32 insertions(+), 21 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs index b84d65271..22bc8ccaa 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs @@ -38,7 +38,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder this.blockAreaSize.Height); this.BlockRowsPerStep = JpegImagePostProcessor.BlockRowsPerStep / this.Component.SubSamplingDivisors.Height; - } /// diff --git a/src/ImageSharp/Image.LoadPixelData.cs b/src/ImageSharp/Image.LoadPixelData.cs index eb7ce261f..1fd5984a1 100644 --- a/src/ImageSharp/Image.LoadPixelData.cs +++ b/src/ImageSharp/Image.LoadPixelData.cs @@ -4,6 +4,7 @@ using System; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp @@ -118,10 +119,10 @@ namespace SixLabors.ImageSharp Guard.MustBeGreaterThanOrEqualTo(data.Length, count, nameof(data)); var image = new Image(config, width, height); - - data.Slice(0, count).CopyTo(image.Frames.RootFrame.GetPixelSpan()); + data = data.Slice(0, count); + data.CopyTo(image.Frames.RootFrame.PixelBuffer.MemoryGroup); return image; } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/ImageFrame.LoadPixelData.cs b/src/ImageSharp/ImageFrame.LoadPixelData.cs index 9e90aeaf5..5b4b3442a 100644 --- a/src/ImageSharp/ImageFrame.LoadPixelData.cs +++ b/src/ImageSharp/ImageFrame.LoadPixelData.cs @@ -4,6 +4,7 @@ using System; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp @@ -43,7 +44,8 @@ namespace SixLabors.ImageSharp var image = new ImageFrame(configuration, width, height); - data.Slice(0, count).CopyTo(image.GetPixelSpan()); + data = data.Slice(0, count); + data.CopyTo(image.PixelBuffer.MemoryGroup); return image; } diff --git a/src/ImageSharp/ImageFrame.cs b/src/ImageSharp/ImageFrame.cs index 235840e77..cbd526662 100644 --- a/src/ImageSharp/ImageFrame.cs +++ b/src/ImageSharp/ImageFrame.cs @@ -3,6 +3,7 @@ using System; using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; @@ -78,7 +79,7 @@ namespace SixLabors.ImageSharp /// Whether to dispose of managed and unmanaged objects. protected abstract void Dispose(bool disposing); - internal abstract void CopyPixelsTo(Span destination) + internal abstract void CopyPixelsTo(MemoryGroup destination) where TDestinationPixel : struct, IPixel; /// diff --git a/src/ImageSharp/ImageFrameCollection{TPixel}.cs b/src/ImageSharp/ImageFrameCollection{TPixel}.cs index 635ed5b77..f6c19eb5c 100644 --- a/src/ImageSharp/ImageFrameCollection{TPixel}.cs +++ b/src/ImageSharp/ImageFrameCollection{TPixel}.cs @@ -351,7 +351,7 @@ namespace SixLabors.ImageSharp this.parent.GetConfiguration(), source.Size(), source.Metadata.DeepClone()); - source.CopyPixelsTo(result.PixelBuffer.GetSingleSpan()); + source.CopyPixelsTo(result.PixelBuffer.MemoryGroup); return result; } } diff --git a/src/ImageSharp/ImageFrame{TPixel}.cs b/src/ImageSharp/ImageFrame{TPixel}.cs index a0bdf3f51..180865834 100644 --- a/src/ImageSharp/ImageFrame{TPixel}.cs +++ b/src/ImageSharp/ImageFrame{TPixel}.cs @@ -218,15 +218,22 @@ namespace SixLabors.ImageSharp this.isDisposed = true; } - internal override void CopyPixelsTo(Span destination) + internal override void CopyPixelsTo(MemoryGroup destination) { if (typeof(TPixel) == typeof(TDestinationPixel)) { - Span dest1 = MemoryMarshal.Cast(destination); - this.PixelBuffer.GetSingleSpan().CopyTo(dest1); + this.PixelBuffer.MemoryGroup.TransformTo(destination, (s, d) => + { + Span d1 = MemoryMarshal.Cast(d); + s.CopyTo(d1); + }); + return; } - PixelOperations.Instance.To(this.GetConfiguration(), this.PixelBuffer.GetSingleSpan(), destination); + this.PixelBuffer.MemoryGroup.TransformTo(destination, (s, d) => + { + PixelOperations.Instance.To(this.GetConfiguration(), s, d); + }); } /// diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupExtensions.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupExtensions.cs index 7d6afbc7b..28da49263 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupExtensions.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupExtensions.cs @@ -126,11 +126,12 @@ namespace SixLabors.ImageSharp.Memory } } - internal static void TransformTo( - this IMemoryGroup source, - IMemoryGroup target, - TransformItemsDelegate transform) - where T : struct + internal static void TransformTo( + this IMemoryGroup source, + IMemoryGroup target, + TransformItemsDelegate transform) + where TSource : struct + where TTarget : struct { Guard.NotNull(source, nameof(source)); Guard.NotNull(target, nameof(target)); @@ -145,14 +146,14 @@ namespace SixLabors.ImageSharp.Memory } long position = 0; - var srcCur = new MemoryGroupCursor(source); - var trgCur = new MemoryGroupCursor(target); + var srcCur = new MemoryGroupCursor(source); + var trgCur = new MemoryGroupCursor(target); while (position < source.TotalLength) { int fwd = Math.Min(srcCur.LookAhead(), trgCur.LookAhead()); - Span srcSpan = srcCur.GetSpan(fwd); - Span trgSpan = trgCur.GetSpan(fwd); + Span srcSpan = srcCur.GetSpan(fwd); + Span trgSpan = trgCur.GetSpan(fwd); transform(srcSpan, trgSpan); srcCur.Forward(fwd); diff --git a/src/ImageSharp/Memory/TransformItemsDelegate{T}.cs b/src/ImageSharp/Memory/TransformItemsDelegate{T}.cs index 898d704f0..31825b7b4 100644 --- a/src/ImageSharp/Memory/TransformItemsDelegate{T}.cs +++ b/src/ImageSharp/Memory/TransformItemsDelegate{T}.cs @@ -5,5 +5,5 @@ using System; namespace SixLabors.ImageSharp.Memory { - internal delegate void TransformItemsDelegate(ReadOnlySpan source, Span target); + internal delegate void TransformItemsDelegate(ReadOnlySpan source, Span target); } From 43ecf8dd17d22ac17b9f169ffa0053a73e90444b Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Fri, 21 Feb 2020 02:39:52 +0100 Subject: [PATCH 60/62] cleanup --- src/Directory.Build.props | 3 +-- src/ImageSharp/ImageSharp.csproj | 3 +-- src/ImageSharp/Memory/Buffer2DExtensions.cs | 6 ------ tests/ImageSharp.Tests/ImageSharp.Tests.csproj | 3 +-- 4 files changed, 3 insertions(+), 12 deletions(-) diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 5e3ad489a..bcf444c75 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -23,8 +23,7 @@ - - + true diff --git a/src/ImageSharp/ImageSharp.csproj b/src/ImageSharp/ImageSharp.csproj index 0d803475a..be0e9032b 100644 --- a/src/ImageSharp/ImageSharp.csproj +++ b/src/ImageSharp/ImageSharp.csproj @@ -10,8 +10,7 @@ $(packageversion) 0.0.1 - - netcoreapp3.1 + netcoreapp3.1;netcoreapp2.1;netstandard2.1;netstandard2.0;netstandard1.3;net472 true true diff --git a/src/ImageSharp/Memory/Buffer2DExtensions.cs b/src/ImageSharp/Memory/Buffer2DExtensions.cs index 297d49816..b9a3d1d24 100644 --- a/src/ImageSharp/Memory/Buffer2DExtensions.cs +++ b/src/ImageSharp/Memory/Buffer2DExtensions.cs @@ -38,9 +38,6 @@ namespace SixLabors.ImageSharp.Memory /// /// Thrown when the backing group is discontiguous. /// - // TODO: Review all usages, should be only used with buffers which do not scale fully with image size! - // Remove [Obsolete], when done! - [Obsolete("TODO: Review all usages!")] internal static Span GetSingleSpan(this Buffer2D buffer) where T : struct { @@ -64,9 +61,6 @@ namespace SixLabors.ImageSharp.Memory /// /// Thrown when the backing group is discontiguous. /// - // TODO: Review all usages, should be only used with buffers which do not scale fully with image size! - // Remove [Obsolete], when done! - [Obsolete("TODO: Review all usages!")] internal static Memory GetSingleMemory(this Buffer2D buffer) where T : struct { diff --git a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj index fdefa38e7..34cdca49a 100644 --- a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj +++ b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj @@ -2,8 +2,7 @@ - - netcoreapp3.1 + netcoreapp3.1;netcoreapp2.1;net472 True True SixLabors.ImageSharp.Tests From 7d8fbf6df87ba1682994a4b4afe79da9fa35e176 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Fri, 21 Feb 2020 03:01:50 +0100 Subject: [PATCH 61/62] implement review suggestions --- .../Advanced/AdvancedImageExtensions.cs | 2 +- src/ImageSharp/Image.Decode.cs | 2 +- src/ImageSharp/Image.LoadPixelData.cs | 2 +- src/ImageSharp/ImageFrame.LoadPixelData.cs | 2 +- .../ImageFrameCollection{TPixel}.cs | 2 +- src/ImageSharp/ImageFrame{TPixel}.cs | 10 ++--- src/ImageSharp/Memory/Buffer2DExtensions.cs | 10 ++--- src/ImageSharp/Memory/Buffer2D{T}.cs | 27 ++++++++----- src/ImageSharp/Memory/BufferArea{T}.cs | 4 +- .../MemoryGroup{T}.Consumed.cs | 2 +- .../MemoryGroup{T}.Owned.cs | 6 ++- .../ImageSharp.Tests/Memory/Buffer2DTests.cs | 39 ++++++++++++------- .../ImageProviders/SolidProvider.cs | 2 +- .../ReferenceCodecs/MagickReferenceDecoder.cs | 2 +- 14 files changed, 67 insertions(+), 45 deletions(-) diff --git a/src/ImageSharp/Advanced/AdvancedImageExtensions.cs b/src/ImageSharp/Advanced/AdvancedImageExtensions.cs index 6bd65022e..a988e22b2 100644 --- a/src/ImageSharp/Advanced/AdvancedImageExtensions.cs +++ b/src/ImageSharp/Advanced/AdvancedImageExtensions.cs @@ -61,7 +61,7 @@ namespace SixLabors.ImageSharp.Advanced /// public static IMemoryGroup GetPixelMemoryGroup(this ImageFrame source) where TPixel : struct, IPixel - => source?.PixelBuffer.MemoryGroup.View ?? throw new ArgumentNullException(nameof(source)); + => source?.PixelBuffer.FastMemoryGroup.View ?? throw new ArgumentNullException(nameof(source)); /// /// Gets the representation of the pixels as a containing the backing pixel data of the image diff --git a/src/ImageSharp/Image.Decode.cs b/src/ImageSharp/Image.Decode.cs index e6bcae45c..5c19a4239 100644 --- a/src/ImageSharp/Image.Decode.cs +++ b/src/ImageSharp/Image.Decode.cs @@ -36,7 +36,7 @@ namespace SixLabors.ImageSharp { Buffer2D uninitializedMemoryBuffer = configuration.MemoryAllocator.Allocate2D(width, height); - return new Image(configuration, uninitializedMemoryBuffer.MemoryGroup, width, height, metadata); + return new Image(configuration, uninitializedMemoryBuffer.FastMemoryGroup, width, height, metadata); } /// diff --git a/src/ImageSharp/Image.LoadPixelData.cs b/src/ImageSharp/Image.LoadPixelData.cs index 1fd5984a1..c655a87d0 100644 --- a/src/ImageSharp/Image.LoadPixelData.cs +++ b/src/ImageSharp/Image.LoadPixelData.cs @@ -120,7 +120,7 @@ namespace SixLabors.ImageSharp var image = new Image(config, width, height); data = data.Slice(0, count); - data.CopyTo(image.Frames.RootFrame.PixelBuffer.MemoryGroup); + data.CopyTo(image.Frames.RootFrame.PixelBuffer.FastMemoryGroup); return image; } diff --git a/src/ImageSharp/ImageFrame.LoadPixelData.cs b/src/ImageSharp/ImageFrame.LoadPixelData.cs index 5b4b3442a..837305d62 100644 --- a/src/ImageSharp/ImageFrame.LoadPixelData.cs +++ b/src/ImageSharp/ImageFrame.LoadPixelData.cs @@ -45,7 +45,7 @@ namespace SixLabors.ImageSharp var image = new ImageFrame(configuration, width, height); data = data.Slice(0, count); - data.CopyTo(image.PixelBuffer.MemoryGroup); + data.CopyTo(image.PixelBuffer.FastMemoryGroup); return image; } diff --git a/src/ImageSharp/ImageFrameCollection{TPixel}.cs b/src/ImageSharp/ImageFrameCollection{TPixel}.cs index f6c19eb5c..b7f1d1bb6 100644 --- a/src/ImageSharp/ImageFrameCollection{TPixel}.cs +++ b/src/ImageSharp/ImageFrameCollection{TPixel}.cs @@ -351,7 +351,7 @@ namespace SixLabors.ImageSharp this.parent.GetConfiguration(), source.Size(), source.Metadata.DeepClone()); - source.CopyPixelsTo(result.PixelBuffer.MemoryGroup); + source.CopyPixelsTo(result.PixelBuffer.FastMemoryGroup); return result; } } diff --git a/src/ImageSharp/ImageFrame{TPixel}.cs b/src/ImageSharp/ImageFrame{TPixel}.cs index 180865834..85488c12d 100644 --- a/src/ImageSharp/ImageFrame{TPixel}.cs +++ b/src/ImageSharp/ImageFrame{TPixel}.cs @@ -131,7 +131,7 @@ namespace SixLabors.ImageSharp Guard.NotNull(source, nameof(source)); this.PixelBuffer = this.GetConfiguration().MemoryAllocator.Allocate2D(source.PixelBuffer.Width, source.PixelBuffer.Height); - source.PixelBuffer.MemoryGroup.CopyTo(this.PixelBuffer.MemoryGroup); + source.PixelBuffer.FastMemoryGroup.CopyTo(this.PixelBuffer.FastMemoryGroup); } /// @@ -186,7 +186,7 @@ namespace SixLabors.ImageSharp throw new ArgumentException("ImageFrame.CopyTo(): target must be of the same size!", nameof(target)); } - this.PixelBuffer.MemoryGroup.CopyTo(target.MemoryGroup); + this.PixelBuffer.FastMemoryGroup.CopyTo(target.FastMemoryGroup); } /// @@ -222,7 +222,7 @@ namespace SixLabors.ImageSharp { if (typeof(TPixel) == typeof(TDestinationPixel)) { - this.PixelBuffer.MemoryGroup.TransformTo(destination, (s, d) => + this.PixelBuffer.FastMemoryGroup.TransformTo(destination, (s, d) => { Span d1 = MemoryMarshal.Cast(d); s.CopyTo(d1); @@ -230,7 +230,7 @@ namespace SixLabors.ImageSharp return; } - this.PixelBuffer.MemoryGroup.TransformTo(destination, (s, d) => + this.PixelBuffer.FastMemoryGroup.TransformTo(destination, (s, d) => { PixelOperations.Instance.To(this.GetConfiguration(), s, d); }); @@ -291,7 +291,7 @@ namespace SixLabors.ImageSharp /// The value to initialize the bitmap with. internal void Clear(TPixel value) { - MemoryGroup group = this.PixelBuffer.MemoryGroup; + MemoryGroup group = this.PixelBuffer.FastMemoryGroup; if (value.Equals(default)) { diff --git a/src/ImageSharp/Memory/Buffer2DExtensions.cs b/src/ImageSharp/Memory/Buffer2DExtensions.cs index b9a3d1d24..8b0f3845e 100644 --- a/src/ImageSharp/Memory/Buffer2DExtensions.cs +++ b/src/ImageSharp/Memory/Buffer2DExtensions.cs @@ -24,7 +24,7 @@ namespace SixLabors.ImageSharp.Memory where T : struct { Guard.NotNull(buffer, nameof(buffer)); - return buffer.MemoryGroup.View; + return buffer.FastMemoryGroup.View; } /// @@ -42,12 +42,12 @@ namespace SixLabors.ImageSharp.Memory where T : struct { Guard.NotNull(buffer, nameof(buffer)); - if (buffer.MemoryGroup.Count > 1) + if (buffer.FastMemoryGroup.Count > 1) { throw new InvalidOperationException("GetSingleSpan is only valid for a single-buffer group!"); } - return buffer.MemoryGroup.Single().Span; + return buffer.FastMemoryGroup.Single().Span; } /// @@ -65,12 +65,12 @@ namespace SixLabors.ImageSharp.Memory where T : struct { Guard.NotNull(buffer, nameof(buffer)); - if (buffer.MemoryGroup.Count > 1) + if (buffer.FastMemoryGroup.Count > 1) { throw new InvalidOperationException("GetSingleMemory is only valid for a single-buffer group!"); } - return buffer.MemoryGroup.Single(); + return buffer.FastMemoryGroup.Single(); } /// diff --git a/src/ImageSharp/Memory/Buffer2D{T}.cs b/src/ImageSharp/Memory/Buffer2D{T}.cs index 831d40641..f22b9a875 100644 --- a/src/ImageSharp/Memory/Buffer2D{T}.cs +++ b/src/ImageSharp/Memory/Buffer2D{T}.cs @@ -28,7 +28,7 @@ namespace SixLabors.ImageSharp.Memory /// The number of rows. internal Buffer2D(MemoryGroup memoryGroup, int width, int height) { - this.MemoryGroup = memoryGroup; + this.FastMemoryGroup = memoryGroup; this.Width = width; this.Height = height; @@ -49,14 +49,20 @@ namespace SixLabors.ImageSharp.Memory public int Height { get; private set; } /// - /// Gets the backing . + /// Gets the backing . + /// + /// The MemoryGroup. + public IMemoryGroup MemoryGroup => this.FastMemoryGroup.View; + + /// + /// Gets the backing without the view abstraction. /// /// /// This property has been kept internal intentionally. - /// It's public counterpart is , + /// It's public counterpart is , /// which only exposes the view of the MemoryGroup. /// - internal MemoryGroup MemoryGroup { get; } + internal MemoryGroup FastMemoryGroup { get; } /// /// Gets a reference to the element at the specified position. @@ -64,9 +70,10 @@ namespace SixLabors.ImageSharp.Memory /// The x coordinate (row) /// The y coordinate (position at row) /// A reference to the element. - internal ref T this[int x, int y] + /// When index is out of range of the buffer. + public ref T this[int x, int y] { - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] get { DebugGuard.MustBeGreaterThanOrEqualTo(x, 0, nameof(x)); @@ -83,7 +90,7 @@ namespace SixLabors.ImageSharp.Memory /// public void Dispose() { - this.MemoryGroup.Dispose(); + this.FastMemoryGroup.Dispose(); this.cachedMemory = default; } @@ -148,7 +155,7 @@ namespace SixLabors.ImageSharp.Memory { DebugGuard.MustBeGreaterThanOrEqualTo(y, 0, nameof(y)); DebugGuard.MustBeLessThan(y, this.Height, nameof(y)); - return this.MemoryGroup.View.GetBoundedSlice(y * this.Width, this.Width); + return this.FastMemoryGroup.View.GetBoundedSlice(y * this.Width, this.Width); } /// @@ -157,12 +164,12 @@ namespace SixLabors.ImageSharp.Memory /// internal static void SwapOrCopyContent(Buffer2D destination, Buffer2D source) { - bool swap = MemoryGroup.SwapOrCopyContent(destination.MemoryGroup, source.MemoryGroup); + bool swap = MemoryGroup.SwapOrCopyContent(destination.FastMemoryGroup, source.FastMemoryGroup); SwapOwnData(destination, source, swap); } [MethodImpl(InliningOptions.ColdPath)] - private Memory GetRowMemorySlow(int y) => this.MemoryGroup.GetBoundedSlice(y * this.Width, this.Width); + private Memory GetRowMemorySlow(int y) => this.FastMemoryGroup.GetBoundedSlice(y * this.Width, this.Width); [MethodImpl(InliningOptions.ColdPath)] private ref T GetElementSlow(int x, int y) diff --git a/src/ImageSharp/Memory/BufferArea{T}.cs b/src/ImageSharp/Memory/BufferArea{T}.cs index b9210e301..f5cbc6953 100644 --- a/src/ImageSharp/Memory/BufferArea{T}.cs +++ b/src/ImageSharp/Memory/BufferArea{T}.cs @@ -94,7 +94,7 @@ namespace SixLabors.ImageSharp.Memory int xx = this.Rectangle.X; int width = this.Rectangle.Width; - return this.DestinationBuffer.MemoryGroup.GetBoundedSlice(yy + xx, width).Span; + return this.DestinationBuffer.FastMemoryGroup.GetBoundedSlice(yy + xx, width).Span; } /// @@ -140,7 +140,7 @@ namespace SixLabors.ImageSharp.Memory // Optimization for when the size of the area is the same as the buffer size. if (this.IsFullBufferArea) { - this.DestinationBuffer.MemoryGroup.Clear(); + this.DestinationBuffer.FastMemoryGroup.Clear(); return; } diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs index 50987d2cd..f1fe4ed9c 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs @@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.Memory internal abstract partial class MemoryGroup { // Analogous to the "consumed" variant of MemorySource - private class Consumed : MemoryGroup + private sealed class Consumed : MemoryGroup { private readonly Memory[] source; diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs index d7d484ceb..b42b90d28 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs @@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.Memory // Analogous to the "owned" variant of MemorySource internal abstract partial class MemoryGroup { - private class Owned : MemoryGroup + private sealed class Owned : MemoryGroup { private IMemoryOwner[] memoryOwners; @@ -25,6 +25,8 @@ namespace SixLabors.ImageSharp.Memory public bool Swappable { get; } + private bool IsDisposed => this.memoryOwners == null; + public override int Count { get @@ -51,7 +53,7 @@ namespace SixLabors.ImageSharp.Memory public override void Dispose() { - if (this.memoryOwners == null) + if (this.IsDisposed) { return; } diff --git a/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs b/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs index ea3419741..ab04b3700 100644 --- a/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs +++ b/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs @@ -46,8 +46,8 @@ namespace SixLabors.ImageSharp.Tests.Memory { Assert.Equal(width, buffer.Width); Assert.Equal(height, buffer.Height); - Assert.Equal(width * height, buffer.MemoryGroup.TotalLength); - Assert.True(buffer.MemoryGroup.BufferLength % width == 0); + Assert.Equal(width * height, buffer.FastMemoryGroup.TotalLength); + Assert.True(buffer.FastMemoryGroup.BufferLength % width == 0); } } @@ -64,7 +64,7 @@ namespace SixLabors.ImageSharp.Tests.Memory { Assert.Equal(width, buffer.Width); Assert.Equal(height, buffer.Height); - Assert.Equal(0, buffer.MemoryGroup.TotalLength); + Assert.Equal(0, buffer.FastMemoryGroup.TotalLength); Assert.Equal(0, buffer.GetSingleSpan().Length); } } @@ -76,7 +76,7 @@ namespace SixLabors.ImageSharp.Tests.Memory this.MemoryAllocator.BufferCapacityInBytes = sizeof(int) * bufferCapacity; using Buffer2D buffer = this.MemoryAllocator.Allocate2DOveraligned(width, height, alignmentMultiplier); - MemoryGroup memoryGroup = buffer.MemoryGroup; + MemoryGroup memoryGroup = buffer.FastMemoryGroup; int expectedAlignment = width * alignmentMultiplier; Assert.Equal(expectedAlignment, memoryGroup.BufferLength); @@ -113,8 +113,8 @@ namespace SixLabors.ImageSharp.Tests.Memory Assert.Equal(width, span.Length); - int expectedSubBufferOffset = (width * y) - (expectedBufferIndex * buffer.MemoryGroup.BufferLength); - Assert.SpanPointsTo(span, buffer.MemoryGroup[expectedBufferIndex], expectedSubBufferOffset); + int expectedSubBufferOffset = (width * y) - (expectedBufferIndex * buffer.FastMemoryGroup.BufferLength); + Assert.SpanPointsTo(span, buffer.FastMemoryGroup[expectedBufferIndex], expectedSubBufferOffset); } } @@ -172,10 +172,10 @@ namespace SixLabors.ImageSharp.Tests.Memory using (Buffer2D buffer = this.MemoryAllocator.Allocate2D(width, height)) { - int bufferIndex = (width * y) / buffer.MemoryGroup.BufferLength; - int subBufferStart = (width * y) - (bufferIndex * buffer.MemoryGroup.BufferLength); + int bufferIndex = (width * y) / buffer.FastMemoryGroup.BufferLength; + int subBufferStart = (width * y) - (bufferIndex * buffer.FastMemoryGroup.BufferLength); - Span span = buffer.MemoryGroup[bufferIndex].Span.Slice(subBufferStart); + Span span = buffer.FastMemoryGroup[bufferIndex].Span.Slice(subBufferStart); ref TestStructs.Foo actual = ref buffer[x, y]; @@ -194,13 +194,13 @@ namespace SixLabors.ImageSharp.Tests.Memory a[1, 3] = 666; b[1, 3] = 444; - Memory aa = a.MemoryGroup.Single(); - Memory bb = b.MemoryGroup.Single(); + Memory aa = a.FastMemoryGroup.Single(); + Memory bb = b.FastMemoryGroup.Single(); Buffer2D.SwapOrCopyContent(a, b); - Assert.Equal(bb, a.MemoryGroup.Single()); - Assert.Equal(aa, b.MemoryGroup.Single()); + 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()); @@ -287,5 +287,18 @@ namespace SixLabors.ImageSharp.Tests.Memory } } } + + [Fact] + public void PublicMemoryGroup_IsMemoryGroupView() + { + using Buffer2D buffer1 = this.MemoryAllocator.Allocate2D(10, 10); + using Buffer2D buffer2 = this.MemoryAllocator.Allocate2D(10, 10); + IMemoryGroup mgBefore = buffer1.MemoryGroup; + + Buffer2D.SwapOrCopyContent(buffer1, buffer2); + + Assert.False(mgBefore.IsValid); + Assert.NotSame(mgBefore, buffer1.MemoryGroup); + } } } diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/SolidProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/SolidProvider.cs index 179680e1a..f8aa04827 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/SolidProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/SolidProvider.cs @@ -54,7 +54,7 @@ namespace SixLabors.ImageSharp.Tests Image image = base.GetImage(); Color color = new Rgba32(this.r, this.g, this.b, this.a); - image.GetRootFramePixelBuffer().MemoryGroup.Fill(color.ToPixel()); + image.GetRootFramePixelBuffer().FastMemoryGroup.Fill(color.ToPixel()); return image; } diff --git a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs index e492efb25..576ae1d9f 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs @@ -54,7 +54,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs { using var magickImage = new MagickImage(stream); var result = new Image(configuration, magickImage.Width, magickImage.Height); - MemoryGroup resultPixels = result.GetRootFramePixelBuffer().MemoryGroup; + MemoryGroup resultPixels = result.GetRootFramePixelBuffer().FastMemoryGroup; using (IPixelCollection pixels = magickImage.GetPixelsUnsafe()) { From cb716af1cc859e45ce4b14266657d91d7b6389f1 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Fri, 21 Feb 2020 12:11:35 +0100 Subject: [PATCH 62/62] optimize RowOctet indexer --- src/ImageSharp/Formats/Jpeg/Components/RowOctet.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/RowOctet.cs b/src/ImageSharp/Formats/Jpeg/Components/RowOctet.cs index 57a134703..8c3daa4d5 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/RowOctet.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/RowOctet.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Memory; @@ -39,6 +40,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components public Span this[int y] { + [MethodImpl(InliningOptions.ShortMethod)] get { // No unsafe tricks, since Span can't be used as a generic argument @@ -52,9 +54,15 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components 5 => this.row5, 6 => this.row6, 7 => this.row7, - _ => throw new IndexOutOfRangeException() + _ => ThrowIndexOutOfRangeException() }; } } + + [MethodImpl(InliningOptions.ColdPath)] + private static Span ThrowIndexOutOfRangeException() + { + throw new IndexOutOfRangeException(); + } } }