diff --git a/src/ImageSharp/Common/Memory/BufferSpan{T}.cs b/src/ImageSharp/Common/Memory/BufferSpan{T}.cs
index 0507173e9..f3cb85884 100644
--- a/src/ImageSharp/Common/Memory/BufferSpan{T}.cs
+++ b/src/ImageSharp/Common/Memory/BufferSpan{T}.cs
@@ -180,12 +180,14 @@ namespace ImageSharp
}
///
- /// Clears `count` elements beginning from the pointed position.
+ /// Clears `count` elements from the beginning of the span.
///
/// The number of elements to clear
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Clear(int count)
{
+ DebugGuard.MustBeLessThanOrEqualTo(count, this.Length, nameof(count));
+
if (count < 256)
{
Unsafe.InitBlock((void*)this.PointerAtOffset, 0, BufferSpan.USizeOf(count));
@@ -196,6 +198,15 @@ namespace ImageSharp
}
}
+ ///
+ /// Clears the the span
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Clear()
+ {
+ this.Clear(this.Length);
+ }
+
[Conditional("DEBUG")]
private static void GuardArrayAndPointer(T[] array, void* pointerToArray)
{
diff --git a/src/ImageSharp/Common/Memory/IPinnedImageBuffer{T}.cs b/src/ImageSharp/Common/Memory/IPinnedImageBuffer{T}.cs
new file mode 100644
index 000000000..374cbed99
--- /dev/null
+++ b/src/ImageSharp/Common/Memory/IPinnedImageBuffer{T}.cs
@@ -0,0 +1,31 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ ///
+ /// An interface that represents a pinned buffer of value type objects
+ /// interpreted as a 2D region of x elements.
+ ///
+ /// The value type.
+ internal interface IPinnedImageBuffer
+ where T : struct
+ {
+ ///
+ /// Gets the width.
+ ///
+ int Width { get; }
+
+ ///
+ /// Gets the height.
+ ///
+ int Height { get; }
+
+ ///
+ /// Gets a to the backing buffer.
+ ///
+ BufferSpan Span { get; }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Common/Memory/PinnedBuffer{T}.cs b/src/ImageSharp/Common/Memory/PinnedBuffer{T}.cs
index 7378a8d64..d58354a71 100644
--- a/src/ImageSharp/Common/Memory/PinnedBuffer{T}.cs
+++ b/src/ImageSharp/Common/Memory/PinnedBuffer{T}.cs
@@ -11,7 +11,7 @@ namespace ImageSharp
using System.Runtime.InteropServices;
///
- /// Manages a pinned buffer of value type data 'T' as a Disposable resource.
+ /// Manages a pinned buffer of value type objects as a Disposable resource.
/// The backing array is either pooled or comes from the outside.
///
/// The value type.
@@ -56,9 +56,9 @@ namespace ImageSharp
///
/// Initializes a new instance of the class.
///
- /// The count of "relevant" elements in 'array'.
/// The array to pin.
- public PinnedBuffer(int length, T[] array)
+ /// The count of "relevant" elements in 'array'.
+ public PinnedBuffer(T[] array, int length)
{
if (array.Length < length)
{
@@ -99,14 +99,19 @@ namespace ImageSharp
///
public IntPtr Pointer { get; private set; }
+ ///
+ /// Gets a to the backing buffer.
+ ///
+ public BufferSpan Span => this;
+
///
/// Converts to an .
///
/// The to convert.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static implicit operator BufferSpan(PinnedBuffer buffer)
+ public static unsafe implicit operator BufferSpan(PinnedBuffer buffer)
{
- return buffer.Slice();
+ return new BufferSpan(buffer.Array, (void*)buffer.Pointer, 0, buffer.Length);
}
///
@@ -114,6 +119,7 @@ namespace ImageSharp
///
/// The desired count of elements. (Minimum size for )
/// The instance
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static PinnedBuffer CreateClean(int count)
{
PinnedBuffer buffer = new PinnedBuffer(count);
@@ -122,24 +128,26 @@ namespace ImageSharp
}
///
- /// Gets a to the beginning of the raw data of the buffer.
+ /// Gets a to an offseted position inside the buffer.
///
+ /// The start
/// The
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public unsafe BufferSpan Slice()
+ public unsafe BufferSpan Slice(int start)
{
- return new BufferSpan(this.Array, (void*)this.Pointer);
+ return new BufferSpan(this.Array, (void*)this.Pointer, start, this.Length - start);
}
///
/// Gets a to an offseted position inside the buffer.
///
- /// The offset
+ /// The start
+ /// The length of the slice
/// The
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public unsafe BufferSpan Slice(int offset)
+ public unsafe BufferSpan Slice(int start, int length)
{
- return new BufferSpan(this.Array, (void*)this.Pointer, offset);
+ return new BufferSpan(this.Array, (void*)this.Pointer, start, length);
}
///
@@ -195,7 +203,7 @@ namespace ImageSharp
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Clear()
{
- this.Slice().Clear(this.Length);
+ ((BufferSpan)this).Clear();
}
///
diff --git a/src/ImageSharp/Common/Memory/PinnedImageBufferExtensions.cs b/src/ImageSharp/Common/Memory/PinnedImageBufferExtensions.cs
new file mode 100644
index 000000000..fcd5b3831
--- /dev/null
+++ b/src/ImageSharp/Common/Memory/PinnedImageBufferExtensions.cs
@@ -0,0 +1,45 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+ using System.Runtime.CompilerServices;
+
+ ///
+ /// Defines extension methods for .
+ ///
+ internal static class PinnedImageBufferExtensions
+ {
+ ///
+ /// Gets a to the row 'y' beginning from the pixel at 'x'.
+ ///
+ /// The buffer
+ /// The x coordinate (position in the row)
+ /// The y (row) coordinate
+ /// The element type
+ /// The
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static BufferSpan GetRowSpan(this IPinnedImageBuffer buffer, int x, int y)
+ where T : struct
+ {
+ return buffer.Span.Slice((y * buffer.Width) + x, buffer.Width - x);
+ }
+
+ ///
+ /// Gets a to the row 'y' beginning from the pixel at 'x'.
+ ///
+ /// The buffer
+ /// The y (row) coordinate
+ /// The element type
+ /// The
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static BufferSpan GetRowSpan(this IPinnedImageBuffer buffer, int y)
+ where T : struct
+ {
+ return buffer.Span.Slice(y * buffer.Width, buffer.Width);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Common/Memory/PinnedImageBuffer{T}.cs b/src/ImageSharp/Common/Memory/PinnedImageBuffer{T}.cs
new file mode 100644
index 000000000..6ec0fc4e5
--- /dev/null
+++ b/src/ImageSharp/Common/Memory/PinnedImageBuffer{T}.cs
@@ -0,0 +1,62 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System;
+
+ ///
+ /// Represents a pinned buffer of value type objects
+ /// interpreted as a 2D region of x elements.
+ ///
+ /// The value type.
+ internal class PinnedImageBuffer : PinnedBuffer, IPinnedImageBuffer
+ where T : struct
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The number of elements in a row
+ /// The number of rows
+ public PinnedImageBuffer(int width, int height)
+ : base(width * height)
+ {
+ this.Width = width;
+ this.Height = height;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The array to pin
+ /// The number of elements in a row
+ /// The number of rows
+ public PinnedImageBuffer(T[] array, int width, int height)
+ : base(array, width * height)
+ {
+ this.Width = width;
+ this.Height = height;
+ }
+
+ ///
+ public int Width { get; }
+
+ ///
+ public int Height { get; }
+
+ ///
+ /// Creates a clean instance of initializing it's elements with 'default(T)'.
+ ///
+ /// The number of elements in a row
+ /// The number of rows
+ /// The instance
+ public static PinnedImageBuffer CreateClean(int width, int height)
+ {
+ PinnedImageBuffer buffer = new PinnedImageBuffer(width, height);
+ buffer.Clear();
+ return buffer;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Image/PixelAccessor{TColor}.cs b/src/ImageSharp/Image/PixelAccessor{TColor}.cs
index d22deec74..e21e3aa46 100644
--- a/src/ImageSharp/Image/PixelAccessor{TColor}.cs
+++ b/src/ImageSharp/Image/PixelAccessor{TColor}.cs
@@ -15,7 +15,7 @@ namespace ImageSharp
/// Provides per-pixel access to generic pixels.
///
/// The pixel format.
- public sealed unsafe class PixelAccessor : IDisposable
+ public sealed unsafe class PixelAccessor : IDisposable, IPinnedImageBuffer
where TColor : struct, IPixel
{
///
@@ -37,7 +37,7 @@ namespace ImageSharp
///
/// The containing the pixel data.
///
- private PinnedBuffer pixelBuffer;
+ private PinnedImageBuffer pixelBuffer;
///
/// Initializes a new instance of the class.
@@ -59,7 +59,7 @@ namespace ImageSharp
/// The width of the image represented by the pixel buffer.
/// The height of the image represented by the pixel buffer.
public PixelAccessor(int width, int height)
- : this(width, height, PinnedBuffer.CreateClean(width * height))
+ : this(width, height, PinnedImageBuffer.CreateClean(width, height))
{
}
@@ -69,7 +69,7 @@ namespace ImageSharp
/// The width of the image represented by the pixel buffer.
/// The height of the image represented by the pixel buffer.
/// The pixel buffer.
- private PixelAccessor(int width, int height, PinnedBuffer pixels)
+ private PixelAccessor(int width, int height, PinnedImageBuffer pixels)
{
Guard.NotNull(pixels, nameof(pixels));
Guard.MustBeGreaterThan(width, 0, nameof(width));
@@ -123,6 +123,9 @@ namespace ImageSharp
///
public ParallelOptions ParallelOptions { get; }
+ ///
+ BufferSpan IPinnedImageBuffer.Span => this.pixelBuffer;
+
private static BulkPixelOperations Operations => BulkPixelOperations.Instance;
///
@@ -244,9 +247,9 @@ namespace ImageSharp
/// The x coordinate
/// The y coordinate
/// The
- internal BufferSpan GetRowPointer(int x, int y)
+ internal BufferSpan GetRowSpan(int x, int y)
{
- return this.pixelBuffer.Slice((y * this.Width) + x);
+ return this.pixelBuffer.Slice((y * this.Width) + x, this.Width - x);
}
///
@@ -288,8 +291,8 @@ namespace ImageSharp
{
for (int y = 0; y < height; y++)
{
- BufferSpan source = area.GetRowPointer(y);
- BufferSpan destination = this.GetRowPointer(targetX, targetY + y);
+ BufferSpan source = area.GetRowSpan(y);
+ BufferSpan destination = this.GetRowSpan(targetX, targetY + y);
Operations.PackFromZyxBytes(source, destination, width);
}
@@ -308,8 +311,8 @@ namespace ImageSharp
{
for (int y = 0; y < height; y++)
{
- BufferSpan source = area.GetRowPointer(y);
- BufferSpan destination = this.GetRowPointer(targetX, targetY + y);
+ BufferSpan source = area.GetRowSpan(y);
+ BufferSpan destination = this.GetRowSpan(targetX, targetY + y);
Operations.PackFromZyxwBytes(source, destination, width);
}
@@ -328,8 +331,8 @@ namespace ImageSharp
{
for (int y = 0; y < height; y++)
{
- BufferSpan source = area.GetRowPointer(y);
- BufferSpan destination = this.GetRowPointer(targetX, targetY + y);
+ BufferSpan source = area.GetRowSpan(y);
+ BufferSpan destination = this.GetRowSpan(targetX, targetY + y);
Operations.PackFromXyzBytes(source, destination, width);
}
@@ -348,8 +351,8 @@ namespace ImageSharp
{
for (int y = 0; y < height; y++)
{
- BufferSpan source = area.GetRowPointer(y);
- BufferSpan destination = this.GetRowPointer(targetX, targetY + y);
+ BufferSpan source = area.GetRowSpan(y);
+ BufferSpan destination = this.GetRowSpan(targetX, targetY + y);
Operations.PackFromXyzwBytes(source, destination, width);
}
}
@@ -367,8 +370,8 @@ namespace ImageSharp
{
for (int y = 0; y < height; y++)
{
- BufferSpan source = this.GetRowPointer(sourceX, sourceY + y);
- BufferSpan destination = area.GetRowPointer(y);
+ BufferSpan source = this.GetRowSpan(sourceX, sourceY + y);
+ BufferSpan destination = area.GetRowSpan(y);
Operations.ToZyxBytes(source, destination, width);
}
}
@@ -386,8 +389,8 @@ namespace ImageSharp
{
for (int y = 0; y < height; y++)
{
- BufferSpan source = this.GetRowPointer(sourceX, sourceY + y);
- BufferSpan destination = area.GetRowPointer(y);
+ BufferSpan source = this.GetRowSpan(sourceX, sourceY + y);
+ BufferSpan destination = area.GetRowSpan(y);
Operations.ToZyxwBytes(source, destination, width);
}
}
@@ -405,8 +408,8 @@ namespace ImageSharp
{
for (int y = 0; y < height; y++)
{
- BufferSpan source = this.GetRowPointer(sourceX, sourceY + y);
- BufferSpan destination = area.GetRowPointer(y);
+ BufferSpan source = this.GetRowSpan(sourceX, sourceY + y);
+ BufferSpan destination = area.GetRowSpan(y);
Operations.ToXyzBytes(source, destination, width);
}
}
@@ -424,15 +427,15 @@ namespace ImageSharp
{
for (int y = 0; y < height; y++)
{
- BufferSpan source = this.GetRowPointer(sourceX, sourceY + y);
- BufferSpan destination = area.GetRowPointer(y);
+ BufferSpan source = this.GetRowSpan(sourceX, sourceY + y);
+ BufferSpan destination = area.GetRowSpan(y);
Operations.ToXyzwBytes(source, destination, width);
}
}
private void SetPixelBufferUnsafe(int width, int height, TColor[] pixels)
{
- this.SetPixelBufferUnsafe(width, height, new PinnedBuffer(width * height, pixels));
+ this.SetPixelBufferUnsafe(width, height, new PinnedImageBuffer(pixels, width, height));
}
///
@@ -441,7 +444,7 @@ namespace ImageSharp
/// The width.
/// The height.
/// The pixel buffer
- private void SetPixelBufferUnsafe(int width, int height, PinnedBuffer pixels)
+ private void SetPixelBufferUnsafe(int width, int height, PinnedImageBuffer pixels)
{
this.pixelBuffer = pixels;
this.pixelsBase = (byte*)pixels.Pointer;
diff --git a/src/ImageSharp/Image/PixelArea{TColor}.cs b/src/ImageSharp/Image/PixelArea{TColor}.cs
index 0a3c5710c..bd10c9b6b 100644
--- a/src/ImageSharp/Image/PixelArea{TColor}.cs
+++ b/src/ImageSharp/Image/PixelArea{TColor}.cs
@@ -206,7 +206,7 @@ namespace ImageSharp
///
/// The y coordinate
/// The
- internal BufferSpan GetRowPointer(int y)
+ internal BufferSpan GetRowSpan(int y)
{
return this.byteBuffer.Slice(y * this.RowStride);
}
diff --git a/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs b/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs
index 141c9d888..60e25aa04 100644
--- a/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs
+++ b/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs
@@ -314,8 +314,8 @@ namespace ImageSharp.Tests.Colors
public PinnedBuffer ActualDestBuffer { get; }
public PinnedBuffer ExpectedDestBuffer { get; }
- public BufferSpan Source => this.SourceBuffer.Slice();
- public BufferSpan ActualDest => this.ActualDestBuffer.Slice();
+ public BufferSpan Source => this.SourceBuffer;
+ public BufferSpan ActualDest => this.ActualDestBuffer;
public TestBuffers(TSource[] source, TDest[] expectedDest)
{
diff --git a/tests/ImageSharp.Tests/Common/PinnedBufferTests.cs b/tests/ImageSharp.Tests/Common/PinnedBufferTests.cs
index 9eb12a536..f7189aadf 100644
--- a/tests/ImageSharp.Tests/Common/PinnedBufferTests.cs
+++ b/tests/ImageSharp.Tests/Common/PinnedBufferTests.cs
@@ -66,18 +66,17 @@
[Fact]
public void CreateClean()
{
- Parallel.For(0, 100,
- i =>
+ for (int i = 0; i < 100; i++)
+ {
+ using (PinnedBuffer buffer = PinnedBuffer.CreateClean(42))
+ {
+ for (int j = 0; j < buffer.Length; j++)
{
- using (PinnedBuffer buffer = PinnedBuffer.CreateClean(42))
- {
- for (int j = 0; j < buffer.Length; j++)
- {
- Assert.Equal(0, buffer.Array[j]);
- buffer.Array[j] = 666;
- }
- }
- });
+ Assert.Equal(0, buffer.Array[j]);
+ buffer.Array[j] = 666;
+ }
+ }
+ }
}
[Fact]
@@ -89,21 +88,72 @@
Assert.True(buffer.IsDisposedOrLostArrayOwnership);
}
+ [Theory]
+ [InlineData(7)]
+ [InlineData(123)]
+ public void CastToSpan(int bufferLength)
+ {
+ using (PinnedBuffer buffer = new PinnedBuffer(bufferLength))
+ {
+ BufferSpan span = buffer;
+
+ Assert.Equal(buffer.Array, span.Array);
+ Assert.Equal(0, span.Start);
+ Assert.Equal(buffer.Pointer, span.PointerAtOffset);
+ Assert.Equal(span.Length, bufferLength);
+ }
+ }
+
[Fact]
- public void Slice()
+ public void Span()
{
- Foo[] a = { new Foo() { A = 1, B = 2 }, new Foo() { A = 3, B = 4 } };
-
- using (PinnedBuffer buffer = new PinnedBuffer(a))
+ using (PinnedBuffer buffer = new PinnedBuffer(42))
{
- BufferSpan arrayPtr = buffer.Slice();
+ BufferSpan span = buffer.Span;
- Assert.Equal(a, arrayPtr.Array);
- Assert.Equal(0, arrayPtr.Start);
- Assert.Equal(buffer.Pointer, arrayPtr.PointerAtOffset);
+ Assert.Equal(buffer.Array, span.Array);
+ Assert.Equal(0, span.Start);
+ Assert.Equal(buffer.Pointer, span.PointerAtOffset);
+ Assert.Equal(span.Length, 42);
}
}
+ public class Slice
+ {
+
+ [Theory]
+ [InlineData(7, 2)]
+ [InlineData(123, 17)]
+ public void WithStartOnly(int bufferLength, int start)
+ {
+ using (PinnedBuffer buffer = new PinnedBuffer(bufferLength))
+ {
+ BufferSpan span = buffer.Slice(start);
+
+ Assert.Equal(buffer.Array, span.Array);
+ Assert.Equal(start, span.Start);
+ Assert.Equal(buffer.Pointer + start * Unsafe.SizeOf(), span.PointerAtOffset);
+ Assert.Equal(span.Length, bufferLength - start);
+ }
+ }
+
+ [Theory]
+ [InlineData(7, 2, 5)]
+ [InlineData(123, 17, 42)]
+ public void WithStartAndLength(int bufferLength, int start, int spanLength)
+ {
+ using (PinnedBuffer buffer = new PinnedBuffer(bufferLength))
+ {
+ BufferSpan span = buffer.Slice(start, spanLength);
+
+ Assert.Equal(buffer.Array, span.Array);
+ Assert.Equal(start, span.Start);
+ Assert.Equal(buffer.Pointer + start * Unsafe.SizeOf(), span.PointerAtOffset);
+ Assert.Equal(span.Length, spanLength);
+ }
+ }
+ }
+
[Fact]
public void UnPinAndTakeArrayOwnership()
{
diff --git a/tests/ImageSharp.Tests/Common/PinnedImageBufferTests.cs b/tests/ImageSharp.Tests/Common/PinnedImageBufferTests.cs
new file mode 100644
index 000000000..fa9730798
--- /dev/null
+++ b/tests/ImageSharp.Tests/Common/PinnedImageBufferTests.cs
@@ -0,0 +1,86 @@
+// ReSharper disable InconsistentNaming
+namespace ImageSharp.Tests.Common
+{
+ using System.Runtime.CompilerServices;
+
+ using Xunit;
+
+ public unsafe class PinnedImageBufferTests
+ {
+ [Theory]
+ [InlineData(7, 42)]
+ [InlineData(1025, 17)]
+ public void Construct(int width, int height)
+ {
+ using (PinnedImageBuffer buffer = new PinnedImageBuffer(width, height))
+ {
+ Assert.Equal(width, buffer.Width);
+ Assert.Equal(height, buffer.Height);
+ Assert.Equal(width * height, buffer.Length);
+ }
+ }
+
+ [Theory]
+ [InlineData(7, 42)]
+ [InlineData(1025, 17)]
+ public void Construct_FromExternalArray(int width, int height)
+ {
+ int[] array = new int[width * height + 10];
+ using (PinnedImageBuffer buffer = new PinnedImageBuffer(array, width, height))
+ {
+ Assert.Equal(width, buffer.Width);
+ Assert.Equal(height, buffer.Height);
+ Assert.Equal(width * height, buffer.Length);
+ }
+ }
+
+
+ [Fact]
+ public void CreateClean()
+ {
+ for (int i = 0; i < 100; i++)
+ {
+ using (PinnedImageBuffer buffer = PinnedImageBuffer.CreateClean(42, 42))
+ {
+ for (int j = 0; j < buffer.Length; j++)
+ {
+ Assert.Equal(0, buffer.Array[j]);
+ buffer.Array[j] = 666;
+ }
+ }
+ }
+ }
+
+ [Theory]
+ [InlineData(7, 42, 0)]
+ [InlineData(7, 42, 10)]
+ [InlineData(17, 42, 41)]
+ public void GetRowSpanY(int width, int height, int y)
+ {
+ using (PinnedImageBuffer buffer = new PinnedImageBuffer(width, height))
+ {
+ BufferSpan span = buffer.GetRowSpan(y);
+
+ Assert.Equal(width * y, span.Start);
+ Assert.Equal(width, span.Length);
+ Assert.Equal(buffer.Pointer + sizeof(int) * width * y, span.PointerAtOffset);
+ }
+ }
+
+ [Theory]
+ [InlineData(7, 42, 0, 0)]
+ [InlineData(7, 42, 3, 10)]
+ [InlineData(17, 42, 0, 41)]
+ public void GetRowSpanXY(int width, int height, int x, int y)
+ {
+ using (PinnedImageBuffer buffer = new PinnedImageBuffer(width, height))
+ {
+ BufferSpan span = buffer.GetRowSpan(x, y);
+
+ Assert.Equal(width * y + x, span.Start);
+ Assert.Equal(width - x, span.Length);
+ Assert.Equal(buffer.Pointer + sizeof(int) * (width * y + x), span.PointerAtOffset);
+ }
+ }
+ }
+}
\ No newline at end of file