diff --git a/src/ImageSharp/Memory/Buffer2DExtensions.cs b/src/ImageSharp/Memory/Buffer2DExtensions.cs
index 019bf73699..bfc0f715b0 100644
--- a/src/ImageSharp/Memory/Buffer2DExtensions.cs
+++ b/src/ImageSharp/Memory/Buffer2DExtensions.cs
@@ -64,5 +64,24 @@ namespace SixLabors.ImageSharp.Memory
{
return new Rectangle(0, 0, buffer.Width, buffer.Height);
}
+
+ ///
+ /// Return a to the subarea represented by 'rectangle'
+ ///
+ /// The element type
+ /// The
+ /// The rectangel subarea
+ /// The
+ public static BufferArea GetArea(this IBuffer2D buffer, Rectangle rectangle)
+ where T : struct => new BufferArea(buffer, rectangle);
+
+ ///
+ /// Return a to the whole area of 'buffer'
+ ///
+ /// The element type
+ /// The
+ /// The
+ public static BufferArea GetArea(this IBuffer2D buffer)
+ where T : struct => new BufferArea(buffer);
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/Memory/BufferArea.cs b/src/ImageSharp/Memory/BufferArea.cs
new file mode 100644
index 0000000000..4ef095b8b2
--- /dev/null
+++ b/src/ImageSharp/Memory/BufferArea.cs
@@ -0,0 +1,63 @@
+using System;
+using System.Runtime.CompilerServices;
+using SixLabors.Primitives;
+
+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
+ internal struct BufferArea
+ where T : struct
+ {
+ public readonly Rectangle Rectangle;
+
+ public BufferArea(IBuffer2D destinationBuffer, Rectangle rectangle)
+ {
+ Guard.MustBeGreaterThanOrEqualTo(rectangle.X, 0, nameof(rectangle));
+ Guard.MustBeGreaterThanOrEqualTo(rectangle.Y, 0, nameof(rectangle));
+ Guard.MustBeLessThan(rectangle.Width, destinationBuffer.Width, nameof(rectangle));
+ Guard.MustBeLessThan(rectangle.Height, destinationBuffer.Height, nameof(rectangle));
+
+ this.DestinationBuffer = destinationBuffer;
+ this.Rectangle = rectangle;
+ }
+
+ public BufferArea(IBuffer2D destinationBuffer)
+ : this(destinationBuffer, destinationBuffer.FullRectangle())
+ {
+ }
+
+ public IBuffer2D DestinationBuffer { get; }
+
+ public Size Size => this.Rectangle.Size;
+
+ public ref T this[int x, int y] => ref this.DestinationBuffer.Span[this.GetIndexOf(x, y)];
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Span GetRowSpan(int y)
+ {
+ int yy = this.GetRowIndex(y);
+ int xx = this.Rectangle.X;
+ int width = this.Rectangle.Width;
+
+ return this.DestinationBuffer.Span.Slice(yy + xx, width);
+ }
+
+ [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)]
+ private int GetRowIndex(int y)
+ {
+ return (y + this.Rectangle.Y) * this.DestinationBuffer.Width;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Memory/BufferArea2D.cs b/src/ImageSharp/Memory/BufferArea2D.cs
deleted file mode 100644
index cb7cc1c630..0000000000
--- a/src/ImageSharp/Memory/BufferArea2D.cs
+++ /dev/null
@@ -1,30 +0,0 @@
-using SixLabors.Primitives;
-
-namespace SixLabors.ImageSharp.Memory
-{
- ///
- /// Represents a rectangular area inside a 2D memory buffer. (Most commonly )
- /// This type is kind-of 2D Span.
- ///
- /// The element type
- internal struct BufferArea2D
- where T : struct
- {
- public IBuffer2D DestinationBuffer { get; }
-
- public readonly Rectangle Rectangle;
-
- public BufferArea2D(IBuffer2D destinationBuffer, Rectangle rectangle)
- {
- this.DestinationBuffer = destinationBuffer;
- this.Rectangle = rectangle;
- }
-
- public BufferArea2D(Buffer2D destinationBuffer)
- : this(destinationBuffer, destinationBuffer.FullRectangle())
- {
- }
-
- public Size Size => this.Rectangle.Size;
- }
-}
\ No newline at end of file
diff --git a/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs b/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs
index 0353057ede..d662a1b3ef 100644
--- a/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs
+++ b/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs
@@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.Tests.Memory
using Xunit;
- public unsafe class Buffer2DTests
+ public class Buffer2DTests
{
// ReSharper disable once ClassNeverInstantiated.Local
private class Assert : Xunit.Assert
diff --git a/tests/ImageSharp.Tests/Memory/BufferAreaTests.cs b/tests/ImageSharp.Tests/Memory/BufferAreaTests.cs
new file mode 100644
index 0000000000..a370134123
--- /dev/null
+++ b/tests/ImageSharp.Tests/Memory/BufferAreaTests.cs
@@ -0,0 +1,102 @@
+// ReSharper disable InconsistentNaming
+namespace SixLabors.ImageSharp.Tests.Memory
+{
+ using System;
+
+ using SixLabors.ImageSharp.Memory;
+ using SixLabors.Primitives;
+
+ using Xunit;
+
+ public class BufferAreaTests
+ {
+ [Fact]
+ public void Construct()
+ {
+ using (var buffer = new Buffer2D(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);
+ }
+ }
+
+ private static Buffer2D CreateTestBuffer(int w, int h)
+ {
+ var buffer = new Buffer2D(w, h);
+ for (int y = 0; y < h; y++)
+ {
+ for (int x = 0; x < w; x++)
+ {
+ buffer[x, y] = y * 100 + x;
+ }
+ }
+ return buffer;
+ }
+
+ [Theory]
+ [InlineData(-1, 1, 0, 0)]
+ [InlineData(1, -1, 0, 0)]
+ [InlineData(0, 0, 1, 0)]
+ [InlineData(0, 0, 0, 42)]
+ public void Construct_WhenRectangleIsOutsideOfBufferBoundaries_Throws(int dx, int dy, int dWidth, int dHeight)
+ {
+ using (var buffer = new Buffer2D(10, 20))
+ {
+ Rectangle r = buffer.FullRectangle();
+
+ r = new Rectangle(r.X+dx, r.Y+dy, r.Width + dWidth, r.Height + dHeight );
+
+ Assert.ThrowsAny(
+ () =>
+ {
+ var area = new BufferArea(buffer, r);
+ });
+ }
+ }
+
+ [Theory]
+ [InlineData(2, 3, 2, 2)]
+ [InlineData(5, 4, 3, 2)]
+ public void Indexer(int rx, int ry, int x, int y)
+ {
+ using (Buffer2D buffer = CreateTestBuffer(20, 30))
+ {
+ Rectangle r = new Rectangle(rx, ry, 5, 6);
+
+ BufferArea area = buffer.GetArea(r);
+
+ 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)
+ {
+ using (Buffer2D buffer = CreateTestBuffer(20, 30))
+ {
+ Rectangle r = new Rectangle(rx, ry, w, h);
+
+ BufferArea area = buffer.GetArea(r);
+
+ Span span = area.GetRowSpan(y);
+
+ Assert.Equal(w, span.Length);
+
+ for (int i = 0; i < w; i++)
+ {
+ int expected = (ry + y) * 100 + rx + i;
+ int value = span[i];
+
+ Assert.Equal(expected, value);
+ }
+ }
+ }
+ }
+}
\ No newline at end of file