diff --git a/src/ImageSharp/Common/Helpers/ThrowHelper.cs b/src/ImageSharp/Common/Helpers/ThrowHelper.cs new file mode 100644 index 0000000000..a832645334 --- /dev/null +++ b/src/ImageSharp/Common/Helpers/ThrowHelper.cs @@ -0,0 +1,17 @@ +namespace ImageSharp +{ + using System; + using System.Runtime.CompilerServices; + + /// + /// Helps removing exception throwing code from hot path by providing non-inlined exception thrower methods. + /// + internal static class ThrowHelper + { + [MethodImpl(MethodImplOptions.NoInlining)] + public static void ThrowArgumentNullException(string paramName) + { + throw new ArgumentNullException(nameof(paramName)); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Common/Memory/ArrayPointer{T}.cs b/src/ImageSharp/Common/Memory/ArrayPointer{T}.cs new file mode 100644 index 0000000000..c3fc32234a --- /dev/null +++ b/src/ImageSharp/Common/Memory/ArrayPointer{T}.cs @@ -0,0 +1,57 @@ +namespace ImageSharp +{ + using System; + using System.Runtime.CompilerServices; + + /// + /// Provides access to elements in an array from a given position. + /// This struct shares many similarities with corefx System.Span<T> but there are differences in functionalities and semantics: + /// - It's not possible to use it with stack objects or pointers to unmanaged memory, only with managed arrays + /// - There is no bounds checking for performance reasons. Therefore we don't need to store length. (However this could be added as DEBUG-only feature.) + /// - Currently the arrays provided to ArrayPointer need to be pinned. This behaviour could be changed using C#7 features. + /// + internal unsafe struct ArrayPointer + where T : struct + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ArrayPointer(T[] array, void* pointerToArray, int offset) + { + // TODO: Use Guard.NotNull() here after optimizing it with ThrowHelper! + if (array == null) + { + ThrowHelper.ThrowArgumentNullException(nameof(array)); + } + this.Array = array; + this.Offset = offset; + this.PointerAtOffset = (IntPtr)pointerToArray + Unsafe.SizeOf()*offset; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ArrayPointer(T[] array, void* pointerToArray) + { + // TODO: Use Guard.NotNull() here after optimizing it with ThrowHelper! + if (array == null) + { + ThrowHelper.ThrowArgumentNullException(nameof(array)); + } + this.Array = array; + this.Offset = 0; + this.PointerAtOffset = (IntPtr)pointerToArray; + } + + public T[] Array { get; private set; } + + public int Offset { get; private set; } + + public IntPtr PointerAtOffset { get; private set; } + + public ArrayPointer Slice(int offset) + { + ArrayPointer result = default(ArrayPointer); + result.Array = this.Array; + result.Offset = this.Offset + offset; + result.PointerAtOffset = this.PointerAtOffset + Unsafe.SizeOf() * offset; + return result; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Common/Helpers/Fast2DArray{T}.cs b/src/ImageSharp/Common/Memory/Fast2DArray{T}.cs similarity index 98% rename from src/ImageSharp/Common/Helpers/Fast2DArray{T}.cs rename to src/ImageSharp/Common/Memory/Fast2DArray{T}.cs index 26ec816ce3..88a9797572 100644 --- a/src/ImageSharp/Common/Helpers/Fast2DArray{T}.cs +++ b/src/ImageSharp/Common/Memory/Fast2DArray{T}.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // diff --git a/tests/ImageSharp.Tests/Common/ArrayPointerTests.cs b/tests/ImageSharp.Tests/Common/ArrayPointerTests.cs new file mode 100644 index 0000000000..1d229f86ab --- /dev/null +++ b/tests/ImageSharp.Tests/Common/ArrayPointerTests.cs @@ -0,0 +1,97 @@ +// ReSharper disable ObjectCreationAsStatement +// ReSharper disable InconsistentNaming +namespace ImageSharp.Tests.Common +{ + using System; + + using Xunit; + + public unsafe class ArrayPointerTests + { + public struct Foo + { + private int a; + + private double b; + + internal static Foo[] CreateArray(int size) + { + Foo[] result = new Foo[size]; + for (int i = 0; i < size; i++) + { + result[i] = new Foo() { a = i, b = i }; + } + return result; + } + } + + [Fact] + public void ConstructWithNullArray_Throws() + { + Assert.Throws( + () => + { + new ArrayPointer(null, (void*)0); + }); + + Assert.Throws( + () => + { + new ArrayPointer(null, (void*)0); + }); + } + + [Fact] + public void ConstructWithoutOffset() + { + Foo[] array = Foo.CreateArray(3); + fixed (Foo* p = array) + { + // Act: + ArrayPointer ap = new ArrayPointer(array, p); + + // Assert: + Assert.Equal(array, ap.Array); + Assert.Equal((IntPtr)p, ap.PointerAtOffset); + } + } + + [Fact] + public void ConstructWithOffset() + { + Foo[] array = Foo.CreateArray(3); + int offset = 2; + fixed (Foo* p = array) + { + // Act: + ArrayPointer ap = new ArrayPointer(array, p, offset); + + // Assert: + Assert.Equal(array, ap.Array); + Assert.Equal(offset, ap.Offset); + Assert.Equal((IntPtr)(p+offset), ap.PointerAtOffset); + } + } + + [Fact] + public void Slice() + { + Foo[] array = Foo.CreateArray(5); + int offset0 = 2; + int offset1 = 2; + int totalOffset = offset0 + offset1; + fixed (Foo* p = array) + { + ArrayPointer ap = new ArrayPointer(array, p, offset0); + + // Act: + ap = ap.Slice(offset1); + + // Assert: + Assert.Equal(array, ap.Array); + Assert.Equal(totalOffset, ap.Offset); + Assert.Equal((IntPtr)(p + totalOffset), ap.PointerAtOffset); + } + } + } +} \ No newline at end of file