diff --git a/src/ImageSharp/ImageFrame{TPixel}.cs b/src/ImageSharp/ImageFrame{TPixel}.cs index 824fd0ec7..8bcdec65b 100644 --- a/src/ImageSharp/ImageFrame{TPixel}.cs +++ b/src/ImageSharp/ImageFrame{TPixel}.cs @@ -195,21 +195,94 @@ namespace SixLabors.ImageSharp return this.PixelBuffer.GetRowSpan(rowIndex); } - public void ProcessPixelRows(PixelAccessorAction processPixels) => throw new NotImplementedException(); + /// + /// Execute to process image pixels in a safe and efficient manner. + /// + /// The defining the pixel operations. + public void ProcessPixelRows(PixelAccessorAction processPixels) + { + Guard.NotNull(processPixels, nameof(processPixels)); + + this.PixelBuffer.FastMemoryGroup.IncreaseRefCounts(); + try + { + var accessor = new PixelAccessor(this.PixelBuffer); + processPixels(accessor); + } + finally + { + this.PixelBuffer.FastMemoryGroup.DecreaseRefCounts(); + } + } + + /// + /// Execute to process pixels of multiple image frames in a safe and efficient manner. + /// + /// The second image frame. + /// The defining the pixel operations. + /// The pixel type of the second image frame. public void ProcessPixelRows( - Image image2, + ImageFrame frame2, PixelAccessorAction processPixels) where TPixel2 : unmanaged, IPixel - => throw new NotImplementedException(); + { + Guard.NotNull(frame2, nameof(frame2)); + Guard.NotNull(processPixels, nameof(processPixels)); + + this.PixelBuffer.FastMemoryGroup.IncreaseRefCounts(); + frame2.PixelBuffer.FastMemoryGroup.IncreaseRefCounts(); + + try + { + var accessor1 = new PixelAccessor(this.PixelBuffer); + var accessor2 = new PixelAccessor(frame2.PixelBuffer); + processPixels(accessor1, accessor2); + } + finally + { + frame2.PixelBuffer.FastMemoryGroup.DecreaseRefCounts(); + this.PixelBuffer.FastMemoryGroup.DecreaseRefCounts(); + } + } + /// + /// Execute to process pixels of multiple image frames in a safe and efficient manner. + /// + /// The second image frame. + /// The third image frame. + /// The defining the pixel operations. + /// The pixel type of the second image frame. + /// The pixel type of the third image frame. public void ProcessPixelRows( - Image image2, - Image image3, + ImageFrame frame2, + ImageFrame frame3, PixelAccessorAction processPixels) where TPixel2 : unmanaged, IPixel where TPixel3 : unmanaged, IPixel - => throw new NotImplementedException(); + { + Guard.NotNull(frame2, nameof(frame2)); + Guard.NotNull(frame3, nameof(frame3)); + Guard.NotNull(processPixels, nameof(processPixels)); + + this.PixelBuffer.FastMemoryGroup.IncreaseRefCounts(); + frame2.PixelBuffer.FastMemoryGroup.IncreaseRefCounts(); + frame3.PixelBuffer.FastMemoryGroup.IncreaseRefCounts(); + + try + { + var accessor1 = new PixelAccessor(this.PixelBuffer); + var accessor2 = new PixelAccessor(frame2.PixelBuffer); + var accessor3 = new PixelAccessor(frame3.PixelBuffer); + processPixels(accessor1, accessor2, accessor3); + } + finally + { + frame3.PixelBuffer.FastMemoryGroup.DecreaseRefCounts(); + frame2.PixelBuffer.FastMemoryGroup.DecreaseRefCounts(); + this.PixelBuffer.FastMemoryGroup.DecreaseRefCounts(); + } + } /// /// Gets the representation of the pixels as a in the source image's pixel format diff --git a/src/ImageSharp/Image{TPixel}.cs b/src/ImageSharp/Image{TPixel}.cs index c1a084611..899d69bb4 100644 --- a/src/ImageSharp/Image{TPixel}.cs +++ b/src/ImageSharp/Image{TPixel}.cs @@ -204,21 +204,101 @@ namespace SixLabors.ImageSharp } } - public void ProcessPixelRows(PixelAccessorAction processPixels) => throw new NotImplementedException(); + /// + /// Execute to process image pixels in a safe and efficient manner. + /// + /// The defining the pixel operations. + public void ProcessPixelRows(PixelAccessorAction processPixels) + { + Guard.NotNull(processPixels, nameof(processPixels)); + Buffer2D buffer = this.Frames.RootFrame.PixelBuffer; + buffer.FastMemoryGroup.IncreaseRefCounts(); + + try + { + var accessor = new PixelAccessor(buffer); + processPixels(accessor); + } + finally + { + buffer.FastMemoryGroup.DecreaseRefCounts(); + } + } + /// + /// Execute to process pixels of multiple images in a safe and efficient manner. + /// + /// The second image. + /// The defining the pixel operations. + /// The pixel type of the second image. public void ProcessPixelRows( Image image2, PixelAccessorAction processPixels) where TPixel2 : unmanaged, IPixel - => throw new NotImplementedException(); + { + Guard.NotNull(image2, nameof(image2)); + Guard.NotNull(processPixels, nameof(processPixels)); + Buffer2D buffer1 = this.Frames.RootFrame.PixelBuffer; + Buffer2D buffer2 = image2.Frames.RootFrame.PixelBuffer; + + buffer1.FastMemoryGroup.IncreaseRefCounts(); + buffer2.FastMemoryGroup.IncreaseRefCounts(); + + try + { + var accessor1 = new PixelAccessor(buffer1); + var accessor2 = new PixelAccessor(buffer2); + processPixels(accessor1, accessor2); + } + finally + { + buffer2.FastMemoryGroup.DecreaseRefCounts(); + buffer1.FastMemoryGroup.DecreaseRefCounts(); + } + } + + /// + /// Execute to process pixels of multiple images in a safe and efficient manner. + /// + /// The second image. + /// The third image. + /// The defining the pixel operations. + /// The pixel type of the second image. + /// The pixel type of the third image. public void ProcessPixelRows( Image image2, Image image3, PixelAccessorAction processPixels) where TPixel2 : unmanaged, IPixel where TPixel3 : unmanaged, IPixel - => throw new NotImplementedException(); + { + Guard.NotNull(image2, nameof(image2)); + Guard.NotNull(image3, nameof(image3)); + Guard.NotNull(processPixels, nameof(processPixels)); + + Buffer2D buffer1 = this.Frames.RootFrame.PixelBuffer; + Buffer2D buffer2 = image2.Frames.RootFrame.PixelBuffer; + Buffer2D buffer3 = image3.Frames.RootFrame.PixelBuffer; + + buffer1.FastMemoryGroup.IncreaseRefCounts(); + buffer2.FastMemoryGroup.IncreaseRefCounts(); + buffer3.FastMemoryGroup.IncreaseRefCounts(); + + try + { + var accessor1 = new PixelAccessor(buffer1); + var accessor2 = new PixelAccessor(buffer2); + var accessor3 = new PixelAccessor(buffer3); + processPixels(accessor1, accessor2, accessor3); + } + finally + { + buffer3.FastMemoryGroup.DecreaseRefCounts(); + buffer2.FastMemoryGroup.DecreaseRefCounts(); + buffer1.FastMemoryGroup.DecreaseRefCounts(); + } + } /// /// Gets the representation of the pixels as a of contiguous memory diff --git a/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.Buffer{T}.cs b/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.Buffer{T}.cs index b2a19fb60..8f2d982ef 100644 --- a/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.Buffer{T}.cs +++ b/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.Buffer{T}.cs @@ -9,38 +9,14 @@ namespace SixLabors.ImageSharp.Memory.Internals { internal partial class UniformUnmanagedMemoryPool { - public unsafe class Buffer : MemoryManager + public class Buffer : UnmanagedBuffer where T : struct { private UniformUnmanagedMemoryPool pool; - private readonly int length; public Buffer(UniformUnmanagedMemoryPool pool, UnmanagedMemoryHandle bufferHandle, int length) - { + : base(bufferHandle, length) => this.pool = pool; - this.BufferHandle = bufferHandle; - this.length = length; - } - - private void* Pointer => (void*)this.BufferHandle.DangerousGetHandle(); - - protected UnmanagedMemoryHandle BufferHandle { get; private set; } - - public override Span GetSpan() => new Span(this.Pointer, this.length); - - /// - public override MemoryHandle Pin(int elementIndex = 0) - { - // Will be released in Unpin - bool unused = false; - this.BufferHandle.DangerousAddRef(ref unused); - - void* pbData = Unsafe.Add(this.Pointer, elementIndex); - return new MemoryHandle(pbData, pinnable: this); - } - - /// - public override void Unpin() => this.BufferHandle.DangerousRelease(); /// protected override void Dispose(bool disposing) @@ -62,7 +38,7 @@ namespace SixLabors.ImageSharp.Memory.Internals } } - public class FinalizableBuffer : Buffer + public sealed class FinalizableBuffer : Buffer where T : struct { public FinalizableBuffer(UniformUnmanagedMemoryPool pool, UnmanagedMemoryHandle bufferHandle, int length) diff --git a/src/ImageSharp/Memory/Allocators/Internals/UnmanagedBuffer{T}.cs b/src/ImageSharp/Memory/Allocators/Internals/UnmanagedBuffer{T}.cs index 14a2f3b5b..c343e44a8 100644 --- a/src/ImageSharp/Memory/Allocators/Internals/UnmanagedBuffer{T}.cs +++ b/src/ImageSharp/Memory/Allocators/Internals/UnmanagedBuffer{T}.cs @@ -13,10 +13,9 @@ namespace SixLabors.ImageSharp.Memory.Internals /// access to unmanaged buffers allocated by . /// /// The element type. - internal sealed unsafe class UnmanagedBuffer : MemoryManager + internal unsafe class UnmanagedBuffer : MemoryManager where T : struct { - private readonly UnmanagedMemoryHandle bufferHandle; private readonly int lengthInElements; /// @@ -24,41 +23,47 @@ namespace SixLabors.ImageSharp.Memory.Internals /// /// The number of elements to allocate. public UnmanagedBuffer(int lengthInElements) + : this(UnmanagedMemoryHandle.Allocate(lengthInElements * Unsafe.SizeOf()), lengthInElements) + { + } + + protected UnmanagedBuffer(UnmanagedMemoryHandle bufferHandle, int lengthInElements) { this.lengthInElements = lengthInElements; - this.bufferHandle = UnmanagedMemoryHandle.Allocate(lengthInElements * Unsafe.SizeOf()); + this.BufferHandle = bufferHandle; } - private void* Pointer => (void*)this.bufferHandle.DangerousGetHandle(); + public UnmanagedMemoryHandle BufferHandle { get; protected set; } + + private void* Pointer => (void*)this.BufferHandle.DangerousGetHandle(); - public override Span GetSpan() - => new Span(this.Pointer, this.lengthInElements); + public override Span GetSpan() => new(this.Pointer, this.lengthInElements); /// public override MemoryHandle Pin(int elementIndex = 0) { // Will be released in Unpin bool unused = false; - this.bufferHandle.DangerousAddRef(ref unused); + this.BufferHandle.DangerousAddRef(ref unused); void* pbData = Unsafe.Add(this.Pointer, elementIndex); return new MemoryHandle(pbData, pinnable: this); } /// - public override void Unpin() => this.bufferHandle.DangerousRelease(); + public override void Unpin() => this.BufferHandle.DangerousRelease(); /// protected override void Dispose(bool disposing) { - if (this.bufferHandle.IsInvalid) + if (this.BufferHandle.IsInvalid) { return; } if (disposing) { - this.bufferHandle.Dispose(); + this.BufferHandle.Dispose(); } } } diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs index 7c0c3b764..b5771f0c7 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs @@ -86,6 +86,31 @@ namespace SixLabors.ImageSharp.Memory return new MemoryGroupEnumerator(this); } + public override void IncreaseRefCounts() + { + this.EnsureNotDisposed(); + bool dummy = default; + foreach (IMemoryOwner memoryOwner in this.memoryOwners) + { + if (memoryOwner is UnmanagedBuffer unmanagedBuffer) + { + unmanagedBuffer.BufferHandle?.DangerousAddRef(ref dummy); + } + } + } + + public override void DecreaseRefCounts() + { + this.EnsureNotDisposed(); + foreach (IMemoryOwner memoryOwner in this.memoryOwners) + { + if (memoryOwner is UnmanagedBuffer unmanagedBuffer) + { + unmanagedBuffer.BufferHandle?.DangerousRelease(); + } + } + } + /// IEnumerator> IEnumerable>.GetEnumerator() { diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs index 9b00d9cbf..f517482d7 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs @@ -270,5 +270,13 @@ namespace SixLabors.ImageSharp.Memory return false; } } + + public virtual void IncreaseRefCounts() + { + } + + public virtual void DecreaseRefCounts() + { + } } } diff --git a/src/ImageSharp/PixelAccessor{TPixel}.cs b/src/ImageSharp/PixelAccessor{TPixel}.cs index 1f4dbf2eb..65ab5dbda 100644 --- a/src/ImageSharp/PixelAccessor{TPixel}.cs +++ b/src/ImageSharp/PixelAccessor{TPixel}.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp @@ -33,7 +34,7 @@ namespace SixLabors.ImageSharp public delegate void PixelAccessorAction( PixelAccessor pixelAccessor1, PixelAccessor pixelAccessor2, - PixelAccessor pixelAccessor3) + PixelAccessor pixelAccessor3) where TPixel1 : unmanaged, IPixel where TPixel2 : unmanaged, IPixel where TPixel3 : unmanaged, IPixel; @@ -45,23 +46,27 @@ namespace SixLabors.ImageSharp public ref struct PixelAccessor where TPixel : unmanaged, IPixel { + private Buffer2D buffer; + + internal PixelAccessor(Buffer2D buffer) => this.buffer = buffer; + /// /// Gets the width of the backing . /// - public int Width { get; } + public int Width => this.buffer.Width; /// /// Gets the height of the backing . /// - public int Height { get; } + public int Height => this.buffer.Height; /// /// Gets the representation of the pixels as a of contiguous memory /// at row beginning from the first pixel on that row. /// - /// The row. + /// The row index. /// The . /// Thrown when row index is out of range. - public Span GetRowSpan(int rowIndex) => throw new NotImplementedException(); + public Span GetRowSpan(int rowIndex) => this.buffer.GetRowSpan(rowIndex); } } diff --git a/tests/ImageSharp.Tests/Image/ImageFrameTests.cs b/tests/ImageSharp.Tests/Image/ImageFrameTests.cs index bbe1a2335..344ebac95 100644 --- a/tests/ImageSharp.Tests/Image/ImageFrameTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageFrameTests.cs @@ -96,5 +96,45 @@ namespace SixLabors.ImageSharp.Tests Assert.Equal("y", ex.ParamName); } } + + public class ProcessPixelRows : ProcessPixelRowsTestBase + { + protected override void ProcessPixelRowsImpl( + Image image, + PixelAccessorAction processPixels) => + image.Frames.RootFrame.ProcessPixelRows(processPixels); + + protected override void ProcessPixelRowsImpl( + Image image1, + Image image2, + PixelAccessorAction processPixels) => + image1.Frames.RootFrame.ProcessPixelRows(image2.Frames.RootFrame, processPixels); + + protected override void ProcessPixelRowsImpl( + Image image1, + Image image2, + Image image3, + PixelAccessorAction processPixels) => + image1.Frames.RootFrame.ProcessPixelRows( + image2.Frames.RootFrame, + image3.Frames.RootFrame, + processPixels); + + [Fact] + public void NullReference_Throws() + { + using var img = new Image(1, 1); + ImageFrame frame = img.Frames.RootFrame; + + Assert.Throws(() => frame.ProcessPixelRows(null)); + + Assert.Throws(() => frame.ProcessPixelRows((ImageFrame)null, (_, _) => { })); + Assert.Throws(() => frame.ProcessPixelRows(frame, frame, null)); + + Assert.Throws(() => frame.ProcessPixelRows((ImageFrame)null, frame, (_, _, _) => { })); + Assert.Throws(() => frame.ProcessPixelRows(frame, (ImageFrame)null, (_, _, _) => { })); + Assert.Throws(() => frame.ProcessPixelRows(frame, frame, null)); + } + } } } diff --git a/tests/ImageSharp.Tests/Image/ImageTests.cs b/tests/ImageSharp.Tests/Image/ImageTests.cs index 709cecc97..b632f4216 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.cs @@ -176,84 +176,39 @@ namespace SixLabors.ImageSharp.Tests } } - - public class ProcessPixelRows + public class ProcessPixelRows : ProcessPixelRowsTestBase { - [Fact] - public void PixelAccessorDimensionsAreCorrect() - { - using var image = new Image(123, 456); - image.ProcessPixelRows(accessor => - { - Assert.Equal(123, accessor.Width); - Assert.Equal(456, accessor.Height); - }); - } + protected override void ProcessPixelRowsImpl( + Image image, + PixelAccessorAction processPixels) => + image.ProcessPixelRows(processPixels); + + protected override void ProcessPixelRowsImpl( + Image image1, + Image image2, + PixelAccessorAction processPixels) => + image1.ProcessPixelRows(image2, processPixels); + + protected override void ProcessPixelRowsImpl( + Image image1, + Image image2, + Image image3, + PixelAccessorAction processPixels) => + image1.ProcessPixelRows(image2, image3, processPixels); [Fact] - public void WritesImagePixels() + public void NullReference_Throws() { - using var image = new Image(256, 256); - image.ProcessPixelRows(accessor => - { - for (int y = 0; y < accessor.Height; y++) - { - Span row = accessor.GetRowSpan(y); - for (int x = 0; x < row.Length; x++) - { - row[x] = new L16((ushort)(x * y)); - } - } - }); - - Buffer2D buffer = image.Frames.RootFrame.PixelBuffer; - for (int y = 0; y < 256; y++) - { - Span row = buffer.GetRowSpan(y); - for (int x = 0; x < 256; x++) - { - int actual = row[x].PackedValue; - Assert.Equal(x * y, actual); - } - } - } + using var img = new Image(1, 1); - [Fact] - public void CopyImagePixels() - { - using var img1 = new Image(256, 256); - Buffer2D buffer = img1.Frames.RootFrame.PixelBuffer; - for (int y = 0; y < 256; y++) - { - Span row = buffer.GetRowSpan(y); - for (int x = 0; x < 256; x++) - { - row[x] = new L16((ushort)(x * y)); - } - } + Assert.Throws(() => img.ProcessPixelRows(null)); - using var img2 = new Image(256, 256); + Assert.Throws(() => img.ProcessPixelRows((Image)null, (_, _) => { })); + Assert.Throws(() => img.ProcessPixelRows(img, img, null)); - img1.ProcessPixelRows(img2, (accessor1, accessor2) => - { - for (int y = 0; y < accessor1.Height; y++) - { - Span row1 = accessor1.GetRowSpan(y); - Span row2 = accessor2.GetRowSpan(accessor2.Height - y - 1); - row1.CopyTo(row2); - } - }); - - buffer = img2.Frames.RootFrame.PixelBuffer; - for (int y = 0; y < 256; y++) - { - Span row = buffer.GetRowSpan(y); - for (int x = 0; x < 256; x++) - { - int actual = row[x].PackedValue; - Assert.Equal(x * (256 - y - 1), actual); - } - } + Assert.Throws(() => img.ProcessPixelRows((Image)null, img, (_, _, _) => { })); + Assert.Throws(() => img.ProcessPixelRows(img, (Image)null, (_, _, _) => { })); + Assert.Throws(() => img.ProcessPixelRows(img, img, null)); } } diff --git a/tests/ImageSharp.Tests/Image/ProcessPixelRowsTestBase.cs b/tests/ImageSharp.Tests/Image/ProcessPixelRowsTestBase.cs new file mode 100644 index 000000000..ac36c285c --- /dev/null +++ b/tests/ImageSharp.Tests/Image/ProcessPixelRowsTestBase.cs @@ -0,0 +1,260 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.Collections.Generic; +using Microsoft.DotNet.RemoteExecutor; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.Memory.Internals; +using SixLabors.ImageSharp.PixelFormats; +using Xunit; + +namespace SixLabors.ImageSharp.Tests +{ + public abstract class ProcessPixelRowsTestBase + { + protected abstract void ProcessPixelRowsImpl( + Image image, + PixelAccessorAction processPixels) + where TPixel : unmanaged, IPixel; + + protected abstract void ProcessPixelRowsImpl( + Image image1, + Image image2, + PixelAccessorAction processPixels) + where TPixel : unmanaged, IPixel; + + protected abstract void ProcessPixelRowsImpl( + Image image1, + Image image2, + Image image3, + PixelAccessorAction processPixels) + where TPixel : unmanaged, IPixel; + + [Fact] + public void PixelAccessorDimensionsAreCorrect() + { + using var image = new Image(123, 456); + this.ProcessPixelRowsImpl(image, accessor => + { + Assert.Equal(123, accessor.Width); + Assert.Equal(456, accessor.Height); + }); + } + + [Fact] + public void WriteImagePixels_SingleImage() + { + using var image = new Image(256, 256); + this.ProcessPixelRowsImpl(image, accessor => + { + for (int y = 0; y < accessor.Height; y++) + { + Span row = accessor.GetRowSpan(y); + for (int x = 0; x < row.Length; x++) + { + row[x] = new L16((ushort)(x * y)); + } + } + }); + + Buffer2D buffer = image.Frames.RootFrame.PixelBuffer; + for (int y = 0; y < 256; y++) + { + Span row = buffer.GetRowSpan(y); + for (int x = 0; x < 256; x++) + { + int actual = row[x].PackedValue; + Assert.Equal(x * y, actual); + } + } + } + + [Fact] + public void WriteImagePixels_MultiImage2() + { + using var img1 = new Image(256, 256); + Buffer2D buffer = img1.Frames.RootFrame.PixelBuffer; + for (int y = 0; y < 256; y++) + { + Span row = buffer.GetRowSpan(y); + for (int x = 0; x < 256; x++) + { + row[x] = new L16((ushort)(x * y)); + } + } + + using var img2 = new Image(256, 256); + + this.ProcessPixelRowsImpl(img1, img2, (accessor1, accessor2) => + { + for (int y = 0; y < accessor1.Height; y++) + { + Span row1 = accessor1.GetRowSpan(y); + Span row2 = accessor2.GetRowSpan(accessor2.Height - y - 1); + row1.CopyTo(row2); + } + }); + + buffer = img2.Frames.RootFrame.PixelBuffer; + for (int y = 0; y < 256; y++) + { + Span row = buffer.GetRowSpan(y); + for (int x = 0; x < 256; x++) + { + int actual = row[x].PackedValue; + Assert.Equal(x * (256 - y - 1), actual); + } + } + } + + [Fact] + public void WriteImagePixels_MultiImage3() + { + using var img1 = new Image(256, 256); + Buffer2D buffer2 = img1.Frames.RootFrame.PixelBuffer; + for (int y = 0; y < 256; y++) + { + Span row = buffer2.GetRowSpan(y); + for (int x = 0; x < 256; x++) + { + row[x] = new L16((ushort)(x * y)); + } + } + + using var img2 = new Image(256, 256); + using var img3 = new Image(256, 256); + + this.ProcessPixelRowsImpl(img1, img2, img3, (accessor1, accessor2, accessor3) => + { + for (int y = 0; y < accessor1.Height; y++) + { + Span row1 = accessor1.GetRowSpan(y); + Span row2 = accessor2.GetRowSpan(accessor2.Height - y - 1); + Span row3 = accessor3.GetRowSpan(y); + row1.CopyTo(row2); + row1.CopyTo(row3); + } + }); + + buffer2 = img2.Frames.RootFrame.PixelBuffer; + Buffer2D buffer3 = img3.Frames.RootFrame.PixelBuffer; + for (int y = 0; y < 256; y++) + { + Span row2 = buffer2.GetRowSpan(y); + Span row3 = buffer3.GetRowSpan(y); + for (int x = 0; x < 256; x++) + { + int actual2 = row2[x].PackedValue; + int actual3 = row3[x].PackedValue; + Assert.Equal(x * (256 - y - 1), actual2); + Assert.Equal(x * y, actual3); + } + } + } + + [Fact] + public void RetainsUnmangedBuffers1() + { + RemoteExecutor.Invoke(RunTest, this.GetType().FullName).Dispose(); + + static void RunTest(string testTypeName) + { + var buffer = new UnmanagedBuffer(100); + var allocator = new MockUnmanagedMemoryAllocator(buffer); + Configuration.Default.MemoryAllocator = allocator; + + var image = new Image(10, 10); + + Assert.Equal(1, UnmanagedMemoryHandle.TotalOutstandingHandles); + GetTest(testTypeName).ProcessPixelRowsImpl(image, _ => + { + buffer.BufferHandle.Dispose(); + Assert.Equal(1, UnmanagedMemoryHandle.TotalOutstandingHandles); + }); + Assert.Equal(0, UnmanagedMemoryHandle.TotalOutstandingHandles); + } + } + + [Fact] + public void RetainsUnmangedBuffers2() + { + RemoteExecutor.Invoke(RunTest, this.GetType().FullName).Dispose(); + + static void RunTest(string testTypeName) + { + var buffer1 = new UnmanagedBuffer(100); + var buffer2 = new UnmanagedBuffer(100); + var allocator = new MockUnmanagedMemoryAllocator(buffer1, buffer2); + Configuration.Default.MemoryAllocator = allocator; + + var image1 = new Image(10, 10); + var image2 = new Image(10, 10); + + Assert.Equal(2, UnmanagedMemoryHandle.TotalOutstandingHandles); + GetTest(testTypeName).ProcessPixelRowsImpl(image1, image2, (_, _) => + { + buffer1.BufferHandle.Dispose(); + buffer2.BufferHandle.Dispose(); + Assert.Equal(2, UnmanagedMemoryHandle.TotalOutstandingHandles); + }); + Assert.Equal(0, UnmanagedMemoryHandle.TotalOutstandingHandles); + } + } + + [Fact] + public void RetainsUnmangedBuffers3() + { + RemoteExecutor.Invoke(RunTest, this.GetType().FullName).Dispose(); + + static void RunTest(string testTypeName) + { + var buffer1 = new UnmanagedBuffer(100); + var buffer2 = new UnmanagedBuffer(100); + var buffer3 = new UnmanagedBuffer(100); + var allocator = new MockUnmanagedMemoryAllocator(buffer1, buffer2, buffer3); + Configuration.Default.MemoryAllocator = allocator; + + var image1 = new Image(10, 10); + var image2 = new Image(10, 10); + var image3 = new Image(10, 10); + + Assert.Equal(3, UnmanagedMemoryHandle.TotalOutstandingHandles); + GetTest(testTypeName).ProcessPixelRowsImpl(image1, image2, image3, (_, _, _) => + { + buffer1.BufferHandle.Dispose(); + buffer2.BufferHandle.Dispose(); + buffer3.BufferHandle.Dispose(); + Assert.Equal(3, UnmanagedMemoryHandle.TotalOutstandingHandles); + }); + Assert.Equal(0, UnmanagedMemoryHandle.TotalOutstandingHandles); + } + } + + private static ProcessPixelRowsTestBase GetTest(string testTypeName) + { + Type type = typeof(ProcessPixelRowsTestBase).Assembly.GetType(testTypeName); + return (ProcessPixelRowsTestBase)Activator.CreateInstance(type); + } + + private class MockUnmanagedMemoryAllocator : MemoryAllocator + where T1 : struct + { + private Stack> buffers = new(); + + public MockUnmanagedMemoryAllocator(params UnmanagedBuffer[] buffers) + { + foreach (UnmanagedBuffer buffer in buffers) + { + this.buffers.Push(buffer); + } + } + + protected internal override int GetBufferCapacityInBytes() => int.MaxValue; + + public override IMemoryOwner Allocate(int length, AllocationOptions options = AllocationOptions.None) => + (IMemoryOwner)this.buffers.Pop(); + } + } +}