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