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