Browse Source

ProcessPixelRows

af/UniformUnmanagedMemoryPoolMemoryAllocator-02-MemoryGuards
Anton Firszov 4 years ago
parent
commit
1df9e25232
  1. 85
      src/ImageSharp/ImageFrame{TPixel}.cs
  2. 86
      src/ImageSharp/Image{TPixel}.cs
  3. 30
      src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.Buffer{T}.cs
  4. 25
      src/ImageSharp/Memory/Allocators/Internals/UnmanagedBuffer{T}.cs
  5. 25
      src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs
  6. 8
      src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs
  7. 15
      src/ImageSharp/PixelAccessor{TPixel}.cs
  8. 40
      tests/ImageSharp.Tests/Image/ImageFrameTests.cs
  9. 97
      tests/ImageSharp.Tests/Image/ImageTests.cs
  10. 260
      tests/ImageSharp.Tests/Image/ProcessPixelRowsTestBase.cs

85
src/ImageSharp/ImageFrame{TPixel}.cs

@ -195,21 +195,94 @@ namespace SixLabors.ImageSharp
return this.PixelBuffer.GetRowSpan(rowIndex);
}
public void ProcessPixelRows(PixelAccessorAction<TPixel> processPixels) => throw new NotImplementedException();
/// <summary>
/// Execute <paramref name="processPixels"/> to process image pixels in a safe and efficient manner.
/// </summary>
/// <param name="processPixels">The <see cref="PixelAccessorAction{TPixel}"/> defining the pixel operations.</param>
public void ProcessPixelRows(PixelAccessorAction<TPixel> processPixels)
{
Guard.NotNull(processPixels, nameof(processPixels));
this.PixelBuffer.FastMemoryGroup.IncreaseRefCounts();
try
{
var accessor = new PixelAccessor<TPixel>(this.PixelBuffer);
processPixels(accessor);
}
finally
{
this.PixelBuffer.FastMemoryGroup.DecreaseRefCounts();
}
}
/// <summary>
/// Execute <paramref name="processPixels"/> to process pixels of multiple image frames in a safe and efficient manner.
/// </summary>
/// <param name="frame2">The second image frame.</param>
/// <param name="processPixels">The <see cref="PixelAccessorAction{TPixel, TPixel2}"/> defining the pixel operations.</param>
/// <typeparam name="TPixel2">The pixel type of the second image frame.</typeparam>
public void ProcessPixelRows<TPixel2>(
Image<TPixel2> image2,
ImageFrame<TPixel2> frame2,
PixelAccessorAction<TPixel, TPixel2> processPixels)
where TPixel2 : unmanaged, IPixel<TPixel2>
=> 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<TPixel>(this.PixelBuffer);
var accessor2 = new PixelAccessor<TPixel2>(frame2.PixelBuffer);
processPixels(accessor1, accessor2);
}
finally
{
frame2.PixelBuffer.FastMemoryGroup.DecreaseRefCounts();
this.PixelBuffer.FastMemoryGroup.DecreaseRefCounts();
}
}
/// <summary>
/// Execute <paramref name="processPixels"/> to process pixels of multiple image frames in a safe and efficient manner.
/// </summary>
/// <param name="frame2">The second image frame.</param>
/// <param name="frame3">The third image frame.</param>
/// <param name="processPixels">The <see cref="PixelAccessorAction{TPixel, TPixel2, TPixel3}"/> defining the pixel operations.</param>
/// <typeparam name="TPixel2">The pixel type of the second image frame.</typeparam>
/// <typeparam name="TPixel3">The pixel type of the third image frame.</typeparam>
public void ProcessPixelRows<TPixel2, TPixel3>(
Image<TPixel2> image2,
Image<TPixel3> image3,
ImageFrame<TPixel2> frame2,
ImageFrame<TPixel3> frame3,
PixelAccessorAction<TPixel, TPixel2, TPixel3> processPixels)
where TPixel2 : unmanaged, IPixel<TPixel2>
where TPixel3 : unmanaged, IPixel<TPixel3>
=> 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<TPixel>(this.PixelBuffer);
var accessor2 = new PixelAccessor<TPixel2>(frame2.PixelBuffer);
var accessor3 = new PixelAccessor<TPixel3>(frame3.PixelBuffer);
processPixels(accessor1, accessor2, accessor3);
}
finally
{
frame3.PixelBuffer.FastMemoryGroup.DecreaseRefCounts();
frame2.PixelBuffer.FastMemoryGroup.DecreaseRefCounts();
this.PixelBuffer.FastMemoryGroup.DecreaseRefCounts();
}
}
/// <summary>
/// Gets the representation of the pixels as a <see cref="Span{T}"/> in the source image's pixel format

86
src/ImageSharp/Image{TPixel}.cs

@ -204,21 +204,101 @@ namespace SixLabors.ImageSharp
}
}
public void ProcessPixelRows(PixelAccessorAction<TPixel> processPixels) => throw new NotImplementedException();
/// <summary>
/// Execute <paramref name="processPixels"/> to process image pixels in a safe and efficient manner.
/// </summary>
/// <param name="processPixels">The <see cref="PixelAccessorAction{TPixel}"/> defining the pixel operations.</param>
public void ProcessPixelRows(PixelAccessorAction<TPixel> processPixels)
{
Guard.NotNull(processPixels, nameof(processPixels));
Buffer2D<TPixel> buffer = this.Frames.RootFrame.PixelBuffer;
buffer.FastMemoryGroup.IncreaseRefCounts();
try
{
var accessor = new PixelAccessor<TPixel>(buffer);
processPixels(accessor);
}
finally
{
buffer.FastMemoryGroup.DecreaseRefCounts();
}
}
/// <summary>
/// Execute <paramref name="processPixels"/> to process pixels of multiple images in a safe and efficient manner.
/// </summary>
/// <param name="image2">The second image.</param>
/// <param name="processPixels">The <see cref="PixelAccessorAction{TPixel, TPixel2}"/> defining the pixel operations.</param>
/// <typeparam name="TPixel2">The pixel type of the second image.</typeparam>
public void ProcessPixelRows<TPixel2>(
Image<TPixel2> image2,
PixelAccessorAction<TPixel, TPixel2> processPixels)
where TPixel2 : unmanaged, IPixel<TPixel2>
=> throw new NotImplementedException();
{
Guard.NotNull(image2, nameof(image2));
Guard.NotNull(processPixels, nameof(processPixels));
Buffer2D<TPixel> buffer1 = this.Frames.RootFrame.PixelBuffer;
Buffer2D<TPixel2> buffer2 = image2.Frames.RootFrame.PixelBuffer;
buffer1.FastMemoryGroup.IncreaseRefCounts();
buffer2.FastMemoryGroup.IncreaseRefCounts();
try
{
var accessor1 = new PixelAccessor<TPixel>(buffer1);
var accessor2 = new PixelAccessor<TPixel2>(buffer2);
processPixels(accessor1, accessor2);
}
finally
{
buffer2.FastMemoryGroup.DecreaseRefCounts();
buffer1.FastMemoryGroup.DecreaseRefCounts();
}
}
/// <summary>
/// Execute <paramref name="processPixels"/> to process pixels of multiple images in a safe and efficient manner.
/// </summary>
/// <param name="image2">The second image.</param>
/// <param name="image3">The third image.</param>
/// <param name="processPixels">The <see cref="PixelAccessorAction{TPixel, TPixel2, TPixel3}"/> defining the pixel operations.</param>
/// <typeparam name="TPixel2">The pixel type of the second image.</typeparam>
/// <typeparam name="TPixel3">The pixel type of the third image.</typeparam>
public void ProcessPixelRows<TPixel2, TPixel3>(
Image<TPixel2> image2,
Image<TPixel3> image3,
PixelAccessorAction<TPixel, TPixel2, TPixel3> processPixels)
where TPixel2 : unmanaged, IPixel<TPixel2>
where TPixel3 : unmanaged, IPixel<TPixel3>
=> throw new NotImplementedException();
{
Guard.NotNull(image2, nameof(image2));
Guard.NotNull(image3, nameof(image3));
Guard.NotNull(processPixels, nameof(processPixels));
Buffer2D<TPixel> buffer1 = this.Frames.RootFrame.PixelBuffer;
Buffer2D<TPixel2> buffer2 = image2.Frames.RootFrame.PixelBuffer;
Buffer2D<TPixel3> buffer3 = image3.Frames.RootFrame.PixelBuffer;
buffer1.FastMemoryGroup.IncreaseRefCounts();
buffer2.FastMemoryGroup.IncreaseRefCounts();
buffer3.FastMemoryGroup.IncreaseRefCounts();
try
{
var accessor1 = new PixelAccessor<TPixel>(buffer1);
var accessor2 = new PixelAccessor<TPixel2>(buffer2);
var accessor3 = new PixelAccessor<TPixel3>(buffer3);
processPixels(accessor1, accessor2, accessor3);
}
finally
{
buffer3.FastMemoryGroup.DecreaseRefCounts();
buffer2.FastMemoryGroup.DecreaseRefCounts();
buffer1.FastMemoryGroup.DecreaseRefCounts();
}
}
/// <summary>
/// Gets the representation of the pixels as a <see cref="Span{T}"/> of contiguous memory

30
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<T> : MemoryManager<T>
public class Buffer<T> : UnmanagedBuffer<T>
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<T> GetSpan() => new Span<T>(this.Pointer, this.length);
/// <inheritdoc />
public override MemoryHandle Pin(int elementIndex = 0)
{
// Will be released in Unpin
bool unused = false;
this.BufferHandle.DangerousAddRef(ref unused);
void* pbData = Unsafe.Add<T>(this.Pointer, elementIndex);
return new MemoryHandle(pbData, pinnable: this);
}
/// <inheritdoc />
public override void Unpin() => this.BufferHandle.DangerousRelease();
/// <inheritdoc />
protected override void Dispose(bool disposing)
@ -62,7 +38,7 @@ namespace SixLabors.ImageSharp.Memory.Internals
}
}
public class FinalizableBuffer<T> : Buffer<T>
public sealed class FinalizableBuffer<T> : Buffer<T>
where T : struct
{
public FinalizableBuffer(UniformUnmanagedMemoryPool pool, UnmanagedMemoryHandle bufferHandle, int length)

25
src/ImageSharp/Memory/Allocators/Internals/UnmanagedBuffer{T}.cs

@ -13,10 +13,9 @@ namespace SixLabors.ImageSharp.Memory.Internals
/// access to unmanaged buffers allocated by <see cref="Marshal.AllocHGlobal(int)"/>.
/// </summary>
/// <typeparam name="T">The element type.</typeparam>
internal sealed unsafe class UnmanagedBuffer<T> : MemoryManager<T>
internal unsafe class UnmanagedBuffer<T> : MemoryManager<T>
where T : struct
{
private readonly UnmanagedMemoryHandle bufferHandle;
private readonly int lengthInElements;
/// <summary>
@ -24,41 +23,47 @@ namespace SixLabors.ImageSharp.Memory.Internals
/// </summary>
/// <param name="lengthInElements">The number of elements to allocate.</param>
public UnmanagedBuffer(int lengthInElements)
: this(UnmanagedMemoryHandle.Allocate(lengthInElements * Unsafe.SizeOf<T>()), lengthInElements)
{
}
protected UnmanagedBuffer(UnmanagedMemoryHandle bufferHandle, int lengthInElements)
{
this.lengthInElements = lengthInElements;
this.bufferHandle = UnmanagedMemoryHandle.Allocate(lengthInElements * Unsafe.SizeOf<T>());
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<T> GetSpan()
=> new Span<T>(this.Pointer, this.lengthInElements);
public override Span<T> GetSpan() => new(this.Pointer, this.lengthInElements);
/// <inheritdoc />
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<T>(this.Pointer, elementIndex);
return new MemoryHandle(pbData, pinnable: this);
}
/// <inheritdoc />
public override void Unpin() => this.bufferHandle.DangerousRelease();
public override void Unpin() => this.BufferHandle.DangerousRelease();
/// <inheritdoc />
protected override void Dispose(bool disposing)
{
if (this.bufferHandle.IsInvalid)
if (this.BufferHandle.IsInvalid)
{
return;
}
if (disposing)
{
this.bufferHandle.Dispose();
this.BufferHandle.Dispose();
}
}
}

25
src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs

@ -86,6 +86,31 @@ namespace SixLabors.ImageSharp.Memory
return new MemoryGroupEnumerator<T>(this);
}
public override void IncreaseRefCounts()
{
this.EnsureNotDisposed();
bool dummy = default;
foreach (IMemoryOwner<T> memoryOwner in this.memoryOwners)
{
if (memoryOwner is UnmanagedBuffer<T> unmanagedBuffer)
{
unmanagedBuffer.BufferHandle?.DangerousAddRef(ref dummy);
}
}
}
public override void DecreaseRefCounts()
{
this.EnsureNotDisposed();
foreach (IMemoryOwner<T> memoryOwner in this.memoryOwners)
{
if (memoryOwner is UnmanagedBuffer<T> unmanagedBuffer)
{
unmanagedBuffer.BufferHandle?.DangerousRelease();
}
}
}
/// <inheritdoc/>
IEnumerator<Memory<T>> IEnumerable<Memory<T>>.GetEnumerator()
{

8
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()
{
}
}
}

15
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<TPixel1, TPixel2, TPixel3>(
PixelAccessor<TPixel1> pixelAccessor1,
PixelAccessor<TPixel2> pixelAccessor2,
PixelAccessor<TPixel2> pixelAccessor3)
PixelAccessor<TPixel3> pixelAccessor3)
where TPixel1 : unmanaged, IPixel<TPixel1>
where TPixel2 : unmanaged, IPixel<TPixel2>
where TPixel3 : unmanaged, IPixel<TPixel3>;
@ -45,23 +46,27 @@ namespace SixLabors.ImageSharp
public ref struct PixelAccessor<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
private Buffer2D<TPixel> buffer;
internal PixelAccessor(Buffer2D<TPixel> buffer) => this.buffer = buffer;
/// <summary>
/// Gets the width of the backing <see cref="Image{TPixel}"/>.
/// </summary>
public int Width { get; }
public int Width => this.buffer.Width;
/// <summary>
/// Gets the height of the backing <see cref="Image{TPixel}"/>.
/// </summary>
public int Height { get; }
public int Height => this.buffer.Height;
/// <summary>
/// Gets the representation of the pixels as a <see cref="Span{T}"/> of contiguous memory
/// at row <paramref name="rowIndex"/> beginning from the first pixel on that row.
/// </summary>
/// <param name="rowIndex">The row.</param>
/// <param name="rowIndex">The row index.</param>
/// <returns>The <see cref="Span{TPixel}"/>.</returns>
/// <exception cref="ArgumentOutOfRangeException">Thrown when row index is out of range.</exception>
public Span<TPixel> GetRowSpan(int rowIndex) => throw new NotImplementedException();
public Span<TPixel> GetRowSpan(int rowIndex) => this.buffer.GetRowSpan(rowIndex);
}
}

40
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<TPixel>(
Image<TPixel> image,
PixelAccessorAction<TPixel> processPixels) =>
image.Frames.RootFrame.ProcessPixelRows(processPixels);
protected override void ProcessPixelRowsImpl<TPixel>(
Image<TPixel> image1,
Image<TPixel> image2,
PixelAccessorAction<TPixel, TPixel> processPixels) =>
image1.Frames.RootFrame.ProcessPixelRows(image2.Frames.RootFrame, processPixels);
protected override void ProcessPixelRowsImpl<TPixel>(
Image<TPixel> image1,
Image<TPixel> image2,
Image<TPixel> image3,
PixelAccessorAction<TPixel, TPixel, TPixel> processPixels) =>
image1.Frames.RootFrame.ProcessPixelRows(
image2.Frames.RootFrame,
image3.Frames.RootFrame,
processPixels);
[Fact]
public void NullReference_Throws()
{
using var img = new Image<Rgb24>(1, 1);
ImageFrame<Rgb24> frame = img.Frames.RootFrame;
Assert.Throws<ArgumentNullException>(() => frame.ProcessPixelRows(null));
Assert.Throws<ArgumentNullException>(() => frame.ProcessPixelRows((ImageFrame<Rgb24>)null, (_, _) => { }));
Assert.Throws<ArgumentNullException>(() => frame.ProcessPixelRows(frame, frame, null));
Assert.Throws<ArgumentNullException>(() => frame.ProcessPixelRows((ImageFrame<Rgb24>)null, frame, (_, _, _) => { }));
Assert.Throws<ArgumentNullException>(() => frame.ProcessPixelRows(frame, (ImageFrame<Rgb24>)null, (_, _, _) => { }));
Assert.Throws<ArgumentNullException>(() => frame.ProcessPixelRows(frame, frame, null));
}
}
}
}

97
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<Rgb24>(123, 456);
image.ProcessPixelRows(accessor =>
{
Assert.Equal(123, accessor.Width);
Assert.Equal(456, accessor.Height);
});
}
protected override void ProcessPixelRowsImpl<TPixel>(
Image<TPixel> image,
PixelAccessorAction<TPixel> processPixels) =>
image.ProcessPixelRows(processPixels);
protected override void ProcessPixelRowsImpl<TPixel>(
Image<TPixel> image1,
Image<TPixel> image2,
PixelAccessorAction<TPixel, TPixel> processPixels) =>
image1.ProcessPixelRows(image2, processPixels);
protected override void ProcessPixelRowsImpl<TPixel>(
Image<TPixel> image1,
Image<TPixel> image2,
Image<TPixel> image3,
PixelAccessorAction<TPixel, TPixel, TPixel> processPixels) =>
image1.ProcessPixelRows(image2, image3, processPixels);
[Fact]
public void WritesImagePixels()
public void NullReference_Throws()
{
using var image = new Image<L16>(256, 256);
image.ProcessPixelRows(accessor =>
{
for (int y = 0; y < accessor.Height; y++)
{
Span<L16> row = accessor.GetRowSpan(y);
for (int x = 0; x < row.Length; x++)
{
row[x] = new L16((ushort)(x * y));
}
}
});
Buffer2D<L16> buffer = image.Frames.RootFrame.PixelBuffer;
for (int y = 0; y < 256; y++)
{
Span<L16> 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<Rgb24>(1, 1);
[Fact]
public void CopyImagePixels()
{
using var img1 = new Image<L16>(256, 256);
Buffer2D<L16> buffer = img1.Frames.RootFrame.PixelBuffer;
for (int y = 0; y < 256; y++)
{
Span<L16> row = buffer.GetRowSpan(y);
for (int x = 0; x < 256; x++)
{
row[x] = new L16((ushort)(x * y));
}
}
Assert.Throws<ArgumentNullException>(() => img.ProcessPixelRows(null));
using var img2 = new Image<L16>(256, 256);
Assert.Throws<ArgumentNullException>(() => img.ProcessPixelRows((Image<Rgb24>)null, (_, _) => { }));
Assert.Throws<ArgumentNullException>(() => img.ProcessPixelRows(img, img, null));
img1.ProcessPixelRows(img2, (accessor1, accessor2) =>
{
for (int y = 0; y < accessor1.Height; y++)
{
Span<L16> row1 = accessor1.GetRowSpan(y);
Span<L16> row2 = accessor2.GetRowSpan(accessor2.Height - y - 1);
row1.CopyTo(row2);
}
});
buffer = img2.Frames.RootFrame.PixelBuffer;
for (int y = 0; y < 256; y++)
{
Span<L16> 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<ArgumentNullException>(() => img.ProcessPixelRows((Image<Rgb24>)null, img, (_, _, _) => { }));
Assert.Throws<ArgumentNullException>(() => img.ProcessPixelRows(img, (Image<Rgb24>)null, (_, _, _) => { }));
Assert.Throws<ArgumentNullException>(() => img.ProcessPixelRows(img, img, null));
}
}

260
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<TPixel>(
Image<TPixel> image,
PixelAccessorAction<TPixel> processPixels)
where TPixel : unmanaged, IPixel<TPixel>;
protected abstract void ProcessPixelRowsImpl<TPixel>(
Image<TPixel> image1,
Image<TPixel> image2,
PixelAccessorAction<TPixel, TPixel> processPixels)
where TPixel : unmanaged, IPixel<TPixel>;
protected abstract void ProcessPixelRowsImpl<TPixel>(
Image<TPixel> image1,
Image<TPixel> image2,
Image<TPixel> image3,
PixelAccessorAction<TPixel, TPixel, TPixel> processPixels)
where TPixel : unmanaged, IPixel<TPixel>;
[Fact]
public void PixelAccessorDimensionsAreCorrect()
{
using var image = new Image<Rgb24>(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<L16>(256, 256);
this.ProcessPixelRowsImpl(image, accessor =>
{
for (int y = 0; y < accessor.Height; y++)
{
Span<L16> row = accessor.GetRowSpan(y);
for (int x = 0; x < row.Length; x++)
{
row[x] = new L16((ushort)(x * y));
}
}
});
Buffer2D<L16> buffer = image.Frames.RootFrame.PixelBuffer;
for (int y = 0; y < 256; y++)
{
Span<L16> 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<L16>(256, 256);
Buffer2D<L16> buffer = img1.Frames.RootFrame.PixelBuffer;
for (int y = 0; y < 256; y++)
{
Span<L16> row = buffer.GetRowSpan(y);
for (int x = 0; x < 256; x++)
{
row[x] = new L16((ushort)(x * y));
}
}
using var img2 = new Image<L16>(256, 256);
this.ProcessPixelRowsImpl(img1, img2, (accessor1, accessor2) =>
{
for (int y = 0; y < accessor1.Height; y++)
{
Span<L16> row1 = accessor1.GetRowSpan(y);
Span<L16> row2 = accessor2.GetRowSpan(accessor2.Height - y - 1);
row1.CopyTo(row2);
}
});
buffer = img2.Frames.RootFrame.PixelBuffer;
for (int y = 0; y < 256; y++)
{
Span<L16> 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<L16>(256, 256);
Buffer2D<L16> buffer2 = img1.Frames.RootFrame.PixelBuffer;
for (int y = 0; y < 256; y++)
{
Span<L16> row = buffer2.GetRowSpan(y);
for (int x = 0; x < 256; x++)
{
row[x] = new L16((ushort)(x * y));
}
}
using var img2 = new Image<L16>(256, 256);
using var img3 = new Image<L16>(256, 256);
this.ProcessPixelRowsImpl(img1, img2, img3, (accessor1, accessor2, accessor3) =>
{
for (int y = 0; y < accessor1.Height; y++)
{
Span<L16> row1 = accessor1.GetRowSpan(y);
Span<L16> row2 = accessor2.GetRowSpan(accessor2.Height - y - 1);
Span<L16> row3 = accessor3.GetRowSpan(y);
row1.CopyTo(row2);
row1.CopyTo(row3);
}
});
buffer2 = img2.Frames.RootFrame.PixelBuffer;
Buffer2D<L16> buffer3 = img3.Frames.RootFrame.PixelBuffer;
for (int y = 0; y < 256; y++)
{
Span<L16> row2 = buffer2.GetRowSpan(y);
Span<L16> 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<L8>(100);
var allocator = new MockUnmanagedMemoryAllocator<L8>(buffer);
Configuration.Default.MemoryAllocator = allocator;
var image = new Image<L8>(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<L8>(100);
var buffer2 = new UnmanagedBuffer<L8>(100);
var allocator = new MockUnmanagedMemoryAllocator<L8>(buffer1, buffer2);
Configuration.Default.MemoryAllocator = allocator;
var image1 = new Image<L8>(10, 10);
var image2 = new Image<L8>(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<L8>(100);
var buffer2 = new UnmanagedBuffer<L8>(100);
var buffer3 = new UnmanagedBuffer<L8>(100);
var allocator = new MockUnmanagedMemoryAllocator<L8>(buffer1, buffer2, buffer3);
Configuration.Default.MemoryAllocator = allocator;
var image1 = new Image<L8>(10, 10);
var image2 = new Image<L8>(10, 10);
var image3 = new Image<L8>(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<T1> : MemoryAllocator
where T1 : struct
{
private Stack<UnmanagedBuffer<T1>> buffers = new();
public MockUnmanagedMemoryAllocator(params UnmanagedBuffer<T1>[] buffers)
{
foreach (UnmanagedBuffer<T1> buffer in buffers)
{
this.buffers.Push(buffer);
}
}
protected internal override int GetBufferCapacityInBytes() => int.MaxValue;
public override IMemoryOwner<T> Allocate<T>(int length, AllocationOptions options = AllocationOptions.None) =>
(IMemoryOwner<T>)this.buffers.Pop();
}
}
}
Loading…
Cancel
Save