diff --git a/src/ImageSharp.Drawing/Brushes/ImageBrush{TColor}.cs b/src/ImageSharp.Drawing/Brushes/ImageBrush{TColor}.cs index 080111f61..718705b39 100644 --- a/src/ImageSharp.Drawing/Brushes/ImageBrush{TColor}.cs +++ b/src/ImageSharp.Drawing/Brushes/ImageBrush{TColor}.cs @@ -119,7 +119,7 @@ namespace ImageSharp.Drawing.Brushes using (PinnedBuffer buffer = new PinnedBuffer(scanlineBuffer)) { - BufferPointer slice = buffer.Slice(offset); + BufferSpan slice = buffer.Slice(offset); for (int xPos = 0; xPos < scanlineWidth; xPos++) { diff --git a/src/ImageSharp.Drawing/Brushes/PatternBrush{TColor}.cs b/src/ImageSharp.Drawing/Brushes/PatternBrush{TColor}.cs index 2b4d3ec73..df492a764 100644 --- a/src/ImageSharp.Drawing/Brushes/PatternBrush{TColor}.cs +++ b/src/ImageSharp.Drawing/Brushes/PatternBrush{TColor}.cs @@ -152,7 +152,7 @@ namespace ImageSharp.Drawing.Brushes using (PinnedBuffer buffer = new PinnedBuffer(scanlineBuffer)) { - BufferPointer slice = buffer.Slice(offset); + BufferSpan slice = buffer.Slice(offset); for (int xPos = 0; xPos < scanlineWidth; xPos++) { diff --git a/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs b/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs index 2c460e88e..46444e550 100644 --- a/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs +++ b/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs @@ -57,7 +57,7 @@ namespace ImageSharp.Drawing.Processors using (PinnedBuffer buffer = new PinnedBuffer(scanlineBuffer)) { - BufferPointer slice = buffer.Slice(offset); + BufferSpan slice = buffer.Slice(offset); for (int xPos = 0; xPos < scanlineWidth; xPos++) { diff --git a/src/ImageSharp.Drawing/Brushes/RecolorBrush{TColor}.cs b/src/ImageSharp.Drawing/Brushes/RecolorBrush{TColor}.cs index 0c6e86643..257eeb3ae 100644 --- a/src/ImageSharp.Drawing/Brushes/RecolorBrush{TColor}.cs +++ b/src/ImageSharp.Drawing/Brushes/RecolorBrush{TColor}.cs @@ -143,7 +143,7 @@ namespace ImageSharp.Drawing.Brushes using (PinnedBuffer buffer = new PinnedBuffer(scanlineBuffer)) { - BufferPointer slice = buffer.Slice(offset); + BufferSpan slice = buffer.Slice(offset); for (int xPos = 0; xPos < scanlineWidth; xPos++) { diff --git a/src/ImageSharp.Drawing/Brushes/SolidBrush{TColor}.cs b/src/ImageSharp.Drawing/Brushes/SolidBrush{TColor}.cs index e001829b0..125b07bca 100644 --- a/src/ImageSharp.Drawing/Brushes/SolidBrush{TColor}.cs +++ b/src/ImageSharp.Drawing/Brushes/SolidBrush{TColor}.cs @@ -91,7 +91,7 @@ namespace ImageSharp.Drawing.Brushes using (PinnedBuffer buffer = new PinnedBuffer(scanlineBuffer)) { - BufferPointer slice = buffer.Slice(offset); + BufferSpan slice = buffer.Slice(offset); for (int xPos = 0; xPos < scanlineWidth; xPos++) { diff --git a/src/ImageSharp/Colors/Color.BulkOperations.cs b/src/ImageSharp/Colors/Color.BulkOperations.cs index 5c040e04c..039fafced 100644 --- a/src/ImageSharp/Colors/Color.BulkOperations.cs +++ b/src/ImageSharp/Colors/Color.BulkOperations.cs @@ -24,8 +24,8 @@ namespace ImageSharp /// SIMD optimized bulk implementation of /// that works only with `count` divisible by . /// - /// The to the source colors. - /// The to the dstination vectors. + /// The to the source colors. + /// The to the dstination vectors. /// The number of pixels to convert. /// /// Implementation adapted from: @@ -38,8 +38,8 @@ namespace ImageSharp /// /// internal static unsafe void ToVector4SimdAligned( - BufferPointer sourceColors, - BufferPointer destVectors, + BufferSpan sourceColors, + BufferSpan destVectors, int count) { int vecSize = Vector.Count; @@ -85,12 +85,12 @@ namespace ImageSharp vf.CopyTo(fTemp, i); } - BufferPointer.Copy(tempBuf, (BufferPointer)destVectors, unpackedRawCount); + BufferSpan.Copy(tempBuf, (BufferSpan)destVectors, unpackedRawCount); } } /// - internal override void ToVector4(BufferPointer sourceColors, BufferPointer destVectors, int count) + internal override void ToVector4(BufferSpan sourceColors, BufferSpan destVectors, int count) { if (count < 256) { @@ -117,7 +117,7 @@ namespace ImageSharp } /// - internal override unsafe void PackFromXyzBytes(BufferPointer sourceBytes, BufferPointer destColors, int count) + internal override unsafe void PackFromXyzBytes(BufferSpan sourceBytes, BufferSpan destColors, int count) { byte* source = (byte*)sourceBytes; byte* destination = (byte*)destColors; @@ -132,7 +132,7 @@ namespace ImageSharp } /// - internal override unsafe void ToXyzBytes(BufferPointer sourceColors, BufferPointer destBytes, int count) + internal override unsafe void ToXyzBytes(BufferSpan sourceColors, BufferSpan destBytes, int count) { byte* source = (byte*)sourceColors; byte* destination = (byte*)destBytes; @@ -149,19 +149,19 @@ namespace ImageSharp } /// - internal override void PackFromXyzwBytes(BufferPointer sourceBytes, BufferPointer destColors, int count) + internal override void PackFromXyzwBytes(BufferSpan sourceBytes, BufferSpan destColors, int count) { - BufferPointer.Copy(sourceBytes, destColors, count); + BufferSpan.Copy(sourceBytes, destColors, count); } /// - internal override void ToXyzwBytes(BufferPointer sourceColors, BufferPointer destBytes, int count) + internal override void ToXyzwBytes(BufferSpan sourceColors, BufferSpan destBytes, int count) { - BufferPointer.Copy(sourceColors, destBytes, count); + BufferSpan.Copy(sourceColors, destBytes, count); } /// - internal override unsafe void PackFromZyxBytes(BufferPointer sourceBytes, BufferPointer destColors, int count) + internal override unsafe void PackFromZyxBytes(BufferSpan sourceBytes, BufferSpan destColors, int count) { byte* source = (byte*)sourceBytes; byte* destination = (byte*)destColors; @@ -176,7 +176,7 @@ namespace ImageSharp } /// - internal override unsafe void ToZyxBytes(BufferPointer sourceColors, BufferPointer destBytes, int count) + internal override unsafe void ToZyxBytes(BufferSpan sourceColors, BufferSpan destBytes, int count) { byte* source = (byte*)sourceColors; byte* destination = (byte*)destBytes; @@ -193,7 +193,7 @@ namespace ImageSharp } /// - internal override unsafe void PackFromZyxwBytes(BufferPointer sourceBytes, BufferPointer destColors, int count) + internal override unsafe void PackFromZyxwBytes(BufferSpan sourceBytes, BufferSpan destColors, int count) { byte* source = (byte*)sourceBytes; byte* destination = (byte*)destColors; @@ -208,7 +208,7 @@ namespace ImageSharp } /// - internal override unsafe void ToZyxwBytes(BufferPointer sourceColors, BufferPointer destBytes, int count) + internal override unsafe void ToZyxwBytes(BufferSpan sourceColors, BufferSpan destBytes, int count) { byte* source = (byte*)sourceColors; byte* destination = (byte*)destBytes; diff --git a/src/ImageSharp/Colors/PackedPixel/BulkPixelOperations{TColor}.cs b/src/ImageSharp/Colors/PackedPixel/BulkPixelOperations{TColor}.cs index 259b1c9b4..7b6169f9c 100644 --- a/src/ImageSharp/Colors/PackedPixel/BulkPixelOperations{TColor}.cs +++ b/src/ImageSharp/Colors/PackedPixel/BulkPixelOperations{TColor}.cs @@ -29,12 +29,12 @@ namespace ImageSharp /// /// Bulk version of /// - /// The to the source vectors. - /// The to the destination colors. + /// The to the source vectors. + /// The to the destination colors. /// The number of pixels to convert. internal virtual void PackFromVector4( - BufferPointer sourceVectors, - BufferPointer destColors, + BufferSpan sourceVectors, + BufferSpan destColors, int count) { Vector4* sp = (Vector4*)sourceVectors.PointerAtOffset; @@ -55,12 +55,12 @@ namespace ImageSharp /// /// Bulk version of . /// - /// The to the source colors. - /// The to the destination vectors. + /// The to the source colors. + /// The to the destination vectors. /// The number of pixels to convert. internal virtual void ToVector4( - BufferPointer sourceColors, - BufferPointer destVectors, + BufferSpan sourceColors, + BufferSpan destVectors, int count) { byte* sp = (byte*)sourceColors; @@ -78,12 +78,12 @@ namespace ImageSharp /// /// Bulk version of that converts data in . /// - /// The to the source bytes. - /// The to the destination colors. + /// The to the source bytes. + /// The to the destination colors. /// The number of pixels to convert. internal virtual void PackFromXyzBytes( - BufferPointer sourceBytes, - BufferPointer destColors, + BufferSpan sourceBytes, + BufferSpan destColors, int count) { byte* sp = (byte*)sourceBytes; @@ -102,15 +102,15 @@ namespace ImageSharp /// /// Bulk version of . /// - /// The to the source colors. - /// The to the destination bytes. + /// The to the source colors. + /// The to the destination bytes. /// The number of pixels to convert. - internal virtual void ToXyzBytes(BufferPointer sourceColors, BufferPointer destBytes, int count) + internal virtual void ToXyzBytes(BufferSpan sourceColors, BufferSpan destBytes, int count) { byte* sp = (byte*)sourceColors; byte[] dest = destBytes.Array; - for (int i = destBytes.Offset; i < destBytes.Offset + (count * 3); i += 3) + for (int i = destBytes.Start; i < destBytes.Start + (count * 3); i += 3) { TColor c = Unsafe.Read(sp); c.ToXyzBytes(dest, i); @@ -121,12 +121,12 @@ namespace ImageSharp /// /// Bulk version of that converts data in . /// - /// The to the source bytes. - /// The to the destination colors. + /// The to the source bytes. + /// The to the destination colors. /// The number of pixels to convert. internal virtual void PackFromXyzwBytes( - BufferPointer sourceBytes, - BufferPointer destColors, + BufferSpan sourceBytes, + BufferSpan destColors, int count) { byte* sp = (byte*)sourceBytes; @@ -145,18 +145,18 @@ namespace ImageSharp /// /// Bulk version of . /// - /// The to the source colors. - /// The to the destination bytes. + /// The to the source colors. + /// The to the destination bytes. /// The number of pixels to convert. internal virtual void ToXyzwBytes( - BufferPointer sourceColors, - BufferPointer destBytes, + BufferSpan sourceColors, + BufferSpan destBytes, int count) { byte* sp = (byte*)sourceColors; byte[] dest = destBytes.Array; - for (int i = destBytes.Offset; i < destBytes.Offset + (count * 4); i += 4) + for (int i = destBytes.Start; i < destBytes.Start + (count * 4); i += 4) { TColor c = Unsafe.Read(sp); c.ToXyzwBytes(dest, i); @@ -167,12 +167,12 @@ namespace ImageSharp /// /// Bulk version of that converts data in . /// - /// The to the source bytes. - /// The to the destination colors. + /// The to the source bytes. + /// The to the destination colors. /// The number of pixels to convert. internal virtual void PackFromZyxBytes( - BufferPointer sourceBytes, - BufferPointer destColors, + BufferSpan sourceBytes, + BufferSpan destColors, int count) { byte* sp = (byte*)sourceBytes; @@ -191,15 +191,15 @@ namespace ImageSharp /// /// Bulk version of . /// - /// The to the source colors. - /// The to the destination bytes. + /// The to the source colors. + /// The to the destination bytes. /// The number of pixels to convert. - internal virtual void ToZyxBytes(BufferPointer sourceColors, BufferPointer destBytes, int count) + internal virtual void ToZyxBytes(BufferSpan sourceColors, BufferSpan destBytes, int count) { byte* sp = (byte*)sourceColors; byte[] dest = destBytes.Array; - for (int i = destBytes.Offset; i < destBytes.Offset + (count * 3); i += 3) + for (int i = destBytes.Start; i < destBytes.Start + (count * 3); i += 3) { TColor c = Unsafe.Read(sp); c.ToZyxBytes(dest, i); @@ -210,12 +210,12 @@ namespace ImageSharp /// /// Bulk version of that converts data in . /// - /// The to the source bytes. - /// The to the destination colors. + /// The to the source bytes. + /// The to the destination colors. /// The number of pixels to convert. internal virtual void PackFromZyxwBytes( - BufferPointer sourceBytes, - BufferPointer destColors, + BufferSpan sourceBytes, + BufferSpan destColors, int count) { byte* sp = (byte*)sourceBytes; @@ -234,18 +234,18 @@ namespace ImageSharp /// /// Bulk version of . /// - /// The to the source colors. - /// The to the destination bytes. + /// The to the source colors. + /// The to the destination bytes. /// The number of pixels to convert. internal virtual void ToZyxwBytes( - BufferPointer sourceColors, - BufferPointer destBytes, + BufferSpan sourceColors, + BufferSpan destBytes, int count) { byte* sp = (byte*)sourceColors; byte[] dest = destBytes.Array; - for (int i = destBytes.Offset; i < destBytes.Offset + (count * 4); i += 4) + for (int i = destBytes.Start; i < destBytes.Start + (count * 4); i += 4) { TColor c = Unsafe.Read(sp); c.ToZyxwBytes(dest, i); diff --git a/src/ImageSharp/Common/Memory/BufferPointer{T}.cs b/src/ImageSharp/Common/Memory/BufferPointer{T}.cs deleted file mode 100644 index 0673efae0..000000000 --- a/src/ImageSharp/Common/Memory/BufferPointer{T}.cs +++ /dev/null @@ -1,157 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp -{ - using System; - using System.Runtime.CompilerServices; - using System.Runtime.InteropServices; - - /// - /// Provides access to elements in an array from a given position. - /// This type shares many similarities with corefx System.Span<T> but there are significant differences in it's functionalities and semantics: - /// - It's not possible to use it with stack objects or pointers to unmanaged memory, only with managed arrays - /// - It's possible to retrieve a reference to the array () so we can pass it to API-s like - /// - 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.) - /// This makes an unsafe type! - /// - Currently the arrays provided to BufferPointer need to be pinned. This behaviour could be changed using C#7 features. - /// - /// The type of elements of the array - internal unsafe struct BufferPointer - where T : struct - { - /// - /// Initializes a new instance of the struct from a pinned array and an offset. - /// - /// The pinned array - /// Pointer to the beginning of array - /// The offset inside the array - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public BufferPointer(T[] array, void* pointerToArray, int offset) - { - DebugGuard.NotNull(array, nameof(array)); - - this.Array = array; - this.Offset = offset; - this.PointerAtOffset = (IntPtr)pointerToArray + (Unsafe.SizeOf() * offset); - } - - /// - /// Initializes a new instance of the struct from a pinned array. - /// - /// The pinned array - /// Pointer to the start of 'array' - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public BufferPointer(T[] array, void* pointerToArray) - { - DebugGuard.NotNull(array, nameof(array)); - - this.Array = array; - this.Offset = 0; - this.PointerAtOffset = (IntPtr)pointerToArray; - } - - /// - /// Gets the array - /// - public T[] Array { get; private set; } - - /// - /// Gets the offset inside - /// - public int Offset { get; private set; } - - /// - /// Gets the offset inside in bytes. - /// - public int ByteOffset => this.Offset * Unsafe.SizeOf(); - - /// - /// Gets the pointer to the offseted array position - /// - public IntPtr PointerAtOffset { get; private set; } - - /// - /// Gets the element at the specified position. - /// - /// The index from the start of this Pointer to the required element. - /// The at the specified position. - public T this[int index] - { - get - { - byte* ptr = ((byte*)this.PointerAtOffset) + BufferPointer.SizeOf(index); - return Unsafe.Read(ptr); - } - } - - /// - /// Convertes instance to a raw 'void*' pointer - /// - /// The to convert - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static explicit operator void*(BufferPointer bufferPointer) - { - return (void*)bufferPointer.PointerAtOffset; - } - - /// - /// Converts instance to a raw 'byte*' pointer - /// - /// The to convert - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static explicit operator byte*(BufferPointer bufferPointer) - { - return (byte*)bufferPointer.PointerAtOffset; - } - - /// - /// Converts instance to - /// setting it's and to correct values. - /// - /// The to convert - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static explicit operator BufferPointer(BufferPointer source) - { - BufferPointer result = default(BufferPointer); - result.Array = Unsafe.As(source.Array); - result.Offset = source.Offset * Unsafe.SizeOf(); - result.PointerAtOffset = source.PointerAtOffset; - return result; - } - - /// - /// Forms a slice out of the given BufferPointer, beginning at 'offset'. - /// - /// The offset in number of elements - /// The offseted (sliced) BufferPointer - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public BufferPointer Slice(int offset) - { - BufferPointer result = default(BufferPointer); - result.Array = this.Array; - result.Offset = this.Offset + offset; - result.PointerAtOffset = this.PointerAtOffset + (Unsafe.SizeOf() * offset); - return result; - } - - /// - /// Clears `count` elements beginning from the pointed position. - /// - /// The number of elements to clear - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Clear(int count) - { - if (count < 256) - { - Unsafe.InitBlock((void*)this.PointerAtOffset, 0, BufferPointer.USizeOf(count)); - } - else - { - System.Array.Clear(this.Array, this.Offset, count); - } - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Common/Memory/BufferPointer.cs b/src/ImageSharp/Common/Memory/BufferSpan.cs similarity index 81% rename from src/ImageSharp/Common/Memory/BufferPointer.cs rename to src/ImageSharp/Common/Memory/BufferSpan.cs index 523889611..42a6fbc6b 100644 --- a/src/ImageSharp/Common/Memory/BufferPointer.cs +++ b/src/ImageSharp/Common/Memory/BufferSpan.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // @@ -11,9 +11,9 @@ namespace ImageSharp using System.Runtime.InteropServices; /// - /// Utility methods for + /// Utility methods for /// - internal static class BufferPointer + internal static class BufferSpan { /// /// It's worth to use Marshal.Copy() or Buffer.BlockCopy() over this size. @@ -24,11 +24,11 @@ namespace ImageSharp /// Copy 'count' number of elements of the same type from 'source' to 'dest' /// /// The element type. - /// The input - /// The destination . + /// The input + /// The destination . /// The number of elements to copy [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Copy(BufferPointer source, BufferPointer destination, int count) + public static void Copy(BufferSpan source, BufferSpan destination, int count) where T : struct { CopyImpl(source, destination, count); @@ -42,7 +42,7 @@ namespace ImageSharp /// The destination buffer. /// The number of elements to copy from 'source' [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Copy(BufferPointer source, BufferPointer destination, int countInSource) + public static void Copy(BufferSpan source, BufferSpan destination, int countInSource) where T : struct { CopyImpl(source, destination, countInSource); @@ -56,14 +56,14 @@ namespace ImageSharp /// The destination buffer"/> /// The number of elements to copy. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static unsafe void Copy(BufferPointer source, BufferPointer destination, int countInDest) + public static unsafe void Copy(BufferSpan source, BufferSpan destination, int countInDest) where T : struct { int byteCount = SizeOf(countInDest); if (byteCount > (int)ByteCountThreshold) { - Marshal.Copy(source.Array, source.Offset, destination.PointerAtOffset, byteCount); + Marshal.Copy(source.Array, source.Start, destination.PointerAtOffset, byteCount); } else { @@ -93,7 +93,7 @@ namespace ImageSharp => (uint)SizeOf(count); [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static unsafe void CopyImpl(BufferPointer source, BufferPointer destination, int count) + private static unsafe void CopyImpl(BufferSpan source, BufferSpan destination, int count) where T : struct where TDest : struct { @@ -103,22 +103,22 @@ namespace ImageSharp { if (Unsafe.SizeOf() == sizeof(long)) { - Marshal.Copy(Unsafe.As(source.Array), source.Offset, destination.PointerAtOffset, count); + Marshal.Copy(Unsafe.As(source.Array), source.Start, destination.PointerAtOffset, count); return; } else if (Unsafe.SizeOf() == sizeof(int)) { - Marshal.Copy(Unsafe.As(source.Array), source.Offset, destination.PointerAtOffset, count); + Marshal.Copy(Unsafe.As(source.Array), source.Start, destination.PointerAtOffset, count); return; } else if (Unsafe.SizeOf() == sizeof(short)) { - Marshal.Copy(Unsafe.As(source.Array), source.Offset, destination.PointerAtOffset, count); + Marshal.Copy(Unsafe.As(source.Array), source.Start, destination.PointerAtOffset, count); return; } else if (Unsafe.SizeOf() == sizeof(byte)) { - Marshal.Copy(Unsafe.As(source.Array), source.Offset, destination.PointerAtOffset, count); + Marshal.Copy(Unsafe.As(source.Array), source.Start, destination.PointerAtOffset, count); return; } } diff --git a/src/ImageSharp/Common/Memory/BufferSpan{T}.cs b/src/ImageSharp/Common/Memory/BufferSpan{T}.cs new file mode 100644 index 000000000..8ef88814c --- /dev/null +++ b/src/ImageSharp/Common/Memory/BufferSpan{T}.cs @@ -0,0 +1,237 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Diagnostics; + using System.Runtime.CompilerServices; + using System.Runtime.InteropServices; + + /// + /// Represents a contiguous region of a pinned managed array. + /// The array is usually owned by a instance. + /// + /// + /// is very similar to corefx System.Span<T>, and we try to maintain a compatible API. + /// There are several differences though: + /// - It's not possible to use it with stack objects or pointers to unmanaged memory, only with managed arrays. + /// - It's possible to retrieve a reference to the array () so we can pass it to API-s like + /// - It's possible to retrieve the pinned pointer. This enables optimized (unchecked) unsafe operations. + /// - There is no bounds checking for performance reasons, only in debug mode. This makes an unsafe type! + /// + /// The type of elements of the array + internal unsafe struct BufferSpan + where T : struct + { + /// + /// Initializes a new instance of the struct from a pinned array and an start. + /// + /// The pinned array + /// Pointer to the beginning of the array + /// The index at which to begin the span. + /// The length + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public BufferSpan(T[] array, void* pointerToArray, int start, int length) + { + GuardArrayAndPointer(array, pointerToArray); + + DebugGuard.MustBeLessThanOrEqualTo(start, array.Length, nameof(start)); + DebugGuard.MustBeLessThanOrEqualTo(length, array.Length - start, nameof(length)); + + this.Array = array; + this.Length = length; + this.Start = start; + this.PointerAtOffset = (IntPtr)pointerToArray + (Unsafe.SizeOf() * start); + } + + /// + /// Initializes a new instance of the struct from a pinned array and an start. + /// + /// The pinned array + /// Pointer to the beginning of the array + /// The index at which to begin the span. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public BufferSpan(T[] array, void* pointerToArray, int start) + { + GuardArrayAndPointer(array, pointerToArray); + DebugGuard.MustBeLessThanOrEqualTo(start, array.Length, nameof(start)); + + this.Array = array; + this.Length = array.Length - start; + this.Start = start; + this.PointerAtOffset = (IntPtr)pointerToArray + (Unsafe.SizeOf() * start); + } + + /// + /// Initializes a new instance of the struct from a pinned array. + /// + /// The pinned array + /// Pointer to the start of 'array' + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public BufferSpan(T[] array, void* pointerToArray) + { + GuardArrayAndPointer(array, pointerToArray); + + this.Array = array; + this.Start = 0; + this.Length = array.Length; + this.PointerAtOffset = (IntPtr)pointerToArray; + } + + /// + /// Gets the backing array + /// + public T[] Array { get; private set; } + + /// + /// Gets the length of the + /// + public int Length { get; private set; } + + /// + /// Gets the start inside + /// + public int Start { get; private set; } + + /// + /// Gets the start inside in bytes. + /// + public int ByteOffset => this.Start * Unsafe.SizeOf(); + + /// + /// Gets the pointer to the offseted array position + /// + public IntPtr PointerAtOffset { get; private set; } + + /// + /// Returns a reference to specified element of the span. + /// + /// The index + /// The reference to the specified element + public ref T this[int index] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + DebugGuard.MustBeLessThan(index, this.Length, nameof(index)); + + byte* ptr = (byte*)this.PointerAtOffset + BufferSpan.SizeOf(index); + return ref Unsafe.AsRef(ptr); + } + } + + /// + /// Convertes instance to a raw 'void*' pointer + /// + /// The to convert + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator void*(BufferSpan bufferSpan) + { + return (void*)bufferSpan.PointerAtOffset; + } + + /// + /// Converts instance to a raw 'byte*' pointer + /// + /// The to convert + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator byte*(BufferSpan bufferSpan) + { + return (byte*)bufferSpan.PointerAtOffset; + } + + /// + /// Converts generic to a of bytes + /// setting it's and to correct values. + /// + /// The to convert + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator BufferSpan(BufferSpan source) + { + BufferSpan result = default(BufferSpan); + result.Array = Unsafe.As(source.Array); + result.Start = source.Start * Unsafe.SizeOf(); + result.PointerAtOffset = source.PointerAtOffset; + return result; + } + + /// + /// Forms a slice out of the given BufferSpan, beginning at 'start'. + /// + /// TThe index at which to begin this slice. + /// The offseted (sliced) BufferSpan + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public BufferSpan Slice(int start) + { + DebugGuard.MustBeLessThan(start, this.Length, nameof(start)); + + BufferSpan result = default(BufferSpan); + result.Array = this.Array; + result.Start = this.Start + start; + result.PointerAtOffset = this.PointerAtOffset + (Unsafe.SizeOf() * start); + result.Length = this.Length - start; + return result; + } + + /// + /// Forms a slice out of the given BufferSpan, beginning at 'start'. + /// + /// The index at which to begin this slice. + /// The desired length for the slice (exclusive). + /// The sliced BufferSpan + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public BufferSpan Slice(int start, int length) + { + DebugGuard.MustBeLessThanOrEqualTo(start, this.Length, nameof(start)); + DebugGuard.MustBeLessThanOrEqualTo(length, this.Length - start, nameof(length)); + + BufferSpan result = default(BufferSpan); + result.Array = this.Array; + result.Start = this.Start + start; + result.PointerAtOffset = this.PointerAtOffset + (Unsafe.SizeOf() * start); + result.Length = length; + return result; + } + + /// + /// 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)); + } + else + { + System.Array.Clear(this.Array, this.Start, count); + } + } + + /// + /// Clears the the span + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Clear() + { + this.Clear(this.Length); + } + + [Conditional("DEBUG")] + private static void GuardArrayAndPointer(T[] array, void* pointerToArray) + { + DebugGuard.NotNull(array, nameof(array)); + DebugGuard.IsFalse( + pointerToArray == (void*)0, + nameof(pointerToArray), + "pointerToArray should not be null pointer!"); + } + } +} \ No newline at end of file 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 d002e08fb..611688c99 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. @@ -32,11 +32,11 @@ namespace ImageSharp /// /// Initializes a new instance of the class. /// - /// The desired count of elements. (Minimum size for ) - public PinnedBuffer(int count) + /// The desired count of elements. (Minimum size for ) + public PinnedBuffer(int length) { - this.Count = count; - this.Array = PixelDataPool.Rent(count); + this.Length = length; + this.Array = PixelDataPool.Rent(length); this.isPoolingOwner = true; this.Pin(); } @@ -47,7 +47,7 @@ namespace ImageSharp /// The array to pin. public PinnedBuffer(T[] array) { - this.Count = array.Length; + this.Length = array.Length; this.Array = array; this.isPoolingOwner = false; this.Pin(); @@ -56,16 +56,16 @@ namespace ImageSharp /// /// Initializes a new instance of the class. /// - /// The count of "relevant" elements in 'array'. /// The array to pin. - public PinnedBuffer(int count, T[] array) + /// The count of "relevant" elements in 'array'. + public PinnedBuffer(T[] array, int length) { - if (array.Length < count) + if (array.Length < length) { throw new ArgumentException("Can't initialize a PinnedBuffer with array.Length < count", nameof(array)); } - this.Count = count; + this.Length = length; this.Array = array; this.isPoolingOwner = false; this.Pin(); @@ -85,9 +85,9 @@ namespace ImageSharp public bool IsDisposedOrLostArrayOwnership { get; private set; } /// - /// Gets the count of "relevant" elements. Usually be smaller than 'Array.Length' when is pooled. + /// Gets the count of "relevant" elements. It's usually smaller than 'Array.Length' when is pooled. /// - public int Count { get; private set; } + public int Length { get; private set; } /// /// Gets the backing pinned array. @@ -100,13 +100,35 @@ namespace ImageSharp public IntPtr Pointer { get; private set; } /// - /// Converts to an . + /// Gets a to the backing buffer. + /// + public BufferSpan Span => this; + + /// + /// Returns a reference to specified element of the buffer. + /// + /// The index + /// The reference to the specified element + public unsafe ref T this[int index] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + DebugGuard.MustBeLessThan(index, this.Length, nameof(index)); + + byte* ptr = (byte*)this.Pointer + BufferSpan.SizeOf(index); + return ref Unsafe.AsRef(ptr); + } + } + + /// + /// Converts to an . /// /// The to convert. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static implicit operator BufferPointer(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 +136,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 +145,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 + /// The start + /// The [MethodImpl(MethodImplOptions.AggressiveInlining)] - public unsafe BufferPointer Slice() + public unsafe BufferSpan Slice(int start) { - return new BufferPointer(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. + /// Gets a to an offseted position inside the buffer. /// - /// The offset - /// The + /// The start + /// The length of the slice + /// The [MethodImpl(MethodImplOptions.AggressiveInlining)] - public unsafe BufferPointer Slice(int offset) + public unsafe BufferSpan Slice(int start, int length) { - return new BufferPointer(this.Array, (void*)this.Pointer, offset); + return new BufferSpan(this.Array, (void*)this.Pointer, start, length); } /// @@ -163,7 +188,7 @@ namespace ImageSharp this.isPoolingOwner = false; this.Array = null; - this.Count = 0; + this.Length = 0; GC.SuppressFinalize(this); } @@ -190,12 +215,12 @@ namespace ImageSharp } /// - /// Clears the buffer, filling elements between 0 and -1 with default(T) + /// Clears the buffer, filling elements between 0 and -1 with default(T) /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Clear() { - this.Slice().Clear(this.Count); + ((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..3ff174c5d --- /dev/null +++ b/src/ImageSharp/Common/Memory/PinnedImageBuffer{T}.cs @@ -0,0 +1,78 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Runtime.CompilerServices; + + /// + /// 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; } + + /// + /// Gets a reference to the element at the specified position. + /// + /// The x coordinate (row) + /// The y coordinate (position at row) + /// A reference to the element. + public ref T this[int x, int y] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + return ref this.Array[(this.Width * y) + x]; + } + } + + /// + /// 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 25e232cf8..f5393cfb3 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; /// @@ -238,17 +241,6 @@ namespace ImageSharp this.CopyTo(area, sourceX, sourceY, width, height); } - /// - /// Gets a to the row 'y' beginning from the pixel at 'x'. - /// - /// The x coordinate - /// The y coordinate - /// The - internal BufferPointer GetRowPointer(int x, int y) - { - return this.pixelBuffer.Slice((y * this.Width) + x); - } - /// /// Sets the pixel buffer in an unsafe manner. This should not be used unless you know what its doing!!! /// @@ -288,8 +280,8 @@ namespace ImageSharp { for (int y = 0; y < height; y++) { - BufferPointer source = area.GetRowPointer(y); - BufferPointer 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 +300,8 @@ namespace ImageSharp { for (int y = 0; y < height; y++) { - BufferPointer source = area.GetRowPointer(y); - BufferPointer 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 +320,8 @@ namespace ImageSharp { for (int y = 0; y < height; y++) { - BufferPointer source = area.GetRowPointer(y); - BufferPointer 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 +340,8 @@ namespace ImageSharp { for (int y = 0; y < height; y++) { - BufferPointer source = area.GetRowPointer(y); - BufferPointer 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 +359,8 @@ namespace ImageSharp { for (int y = 0; y < height; y++) { - BufferPointer source = this.GetRowPointer(sourceX, sourceY + y); - BufferPointer destination = area.GetRowPointer(y); + BufferSpan source = this.GetRowSpan(sourceX, sourceY + y); + BufferSpan destination = area.GetRowSpan(y); Operations.ToZyxBytes(source, destination, width); } } @@ -386,8 +378,8 @@ namespace ImageSharp { for (int y = 0; y < height; y++) { - BufferPointer source = this.GetRowPointer(sourceX, sourceY + y); - BufferPointer destination = area.GetRowPointer(y); + BufferSpan source = this.GetRowSpan(sourceX, sourceY + y); + BufferSpan destination = area.GetRowSpan(y); Operations.ToZyxwBytes(source, destination, width); } } @@ -405,8 +397,8 @@ namespace ImageSharp { for (int y = 0; y < height; y++) { - BufferPointer source = this.GetRowPointer(sourceX, sourceY + y); - BufferPointer destination = area.GetRowPointer(y); + BufferSpan source = this.GetRowSpan(sourceX, sourceY + y); + BufferSpan destination = area.GetRowSpan(y); Operations.ToXyzBytes(source, destination, width); } } @@ -424,15 +416,15 @@ namespace ImageSharp { for (int y = 0; y < height; y++) { - BufferPointer source = this.GetRowPointer(sourceX, sourceY + y); - BufferPointer 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 +433,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 8f2fa5b7a..bd10c9b6b 100644 --- a/src/ImageSharp/Image/PixelArea{TColor}.cs +++ b/src/ImageSharp/Image/PixelArea{TColor}.cs @@ -202,11 +202,11 @@ namespace ImageSharp } /// - /// Gets a to the row y. + /// Gets a to the row y. /// /// The y coordinate - /// The - internal BufferPointer GetRowPointer(int y) + /// The + internal BufferSpan GetRowSpan(int y) { return this.byteBuffer.Slice(y * this.RowStride); } diff --git a/src/ImageSharp/Processing/Processors/Transforms/CompandingResizeProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/CompandingResizeProcessor.cs deleted file mode 100644 index f5314d448..000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/CompandingResizeProcessor.cs +++ /dev/null @@ -1,171 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Processing.Processors -{ - using System; - using System.Numerics; - using System.Threading.Tasks; - - /// - /// Provides methods that allow the resizing of images using various algorithms. - /// This version will expand and compress the image to and from a linear color space during processing. - /// - /// The pixel format. - internal class CompandingResizeProcessor : ResamplingWeightedProcessor - where TColor : struct, IPixel - { - /// - /// Initializes a new instance of the class. - /// - /// The sampler to perform the resize operation. - /// The target width. - /// The target height. - public CompandingResizeProcessor(IResampler sampler, int width, int height) - : base(sampler, width, height, new Rectangle(0, 0, width, height)) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The sampler to perform the resize operation. - /// The target width. - /// The target height. - /// - /// The structure that specifies the portion of the target image object to draw to. - /// - public CompandingResizeProcessor(IResampler sampler, int width, int height, Rectangle resizeRectangle) - : base(sampler, width, height, resizeRectangle) - { - } - - /// - public override bool Compand { get; set; } = true; - - /// - protected override void OnApply(ImageBase source, Rectangle sourceRectangle) - { - // Jump out, we'll deal with that later. - if (source.Width == this.Width && source.Height == this.Height && sourceRectangle == this.ResizeRectangle) - { - return; - } - - int width = this.Width; - int height = this.Height; - int sourceX = sourceRectangle.X; - int sourceY = sourceRectangle.Y; - int startY = this.ResizeRectangle.Y; - int endY = this.ResizeRectangle.Bottom; - int startX = this.ResizeRectangle.X; - int endX = this.ResizeRectangle.Right; - - int minX = Math.Max(0, startX); - int maxX = Math.Min(width, endX); - int minY = Math.Max(0, startY); - int maxY = Math.Min(height, endY); - - if (this.Sampler is NearestNeighborResampler) - { - // Scaling factors - float widthFactor = sourceRectangle.Width / (float)this.ResizeRectangle.Width; - float heightFactor = sourceRectangle.Height / (float)this.ResizeRectangle.Height; - - using (PixelAccessor targetPixels = new PixelAccessor(width, height)) - { - using (PixelAccessor sourcePixels = source.Lock()) - { - Parallel.For( - minY, - maxY, - this.ParallelOptions, - y => - { - // Y coordinates of source points - int originY = (int)(((y - startY) * heightFactor) + sourceY); - - for (int x = minX; x < maxX; x++) - { - // X coordinates of source points - targetPixels[x, y] = sourcePixels[(int)(((x - startX) * widthFactor) + sourceX), originY]; - } - }); - } - - // Break out now. - source.SwapPixelsBuffers(targetPixels); - return; - } - } - - // Interpolate the image using the calculated weights. - // A 2-pass 1D algorithm appears to be faster than splitting a 1-pass 2D algorithm - // First process the columns. Since we are not using multiple threads startY and endY - // are the upper and lower bounds of the source rectangle. - using (PixelAccessor targetPixels = new PixelAccessor(width, height)) - { - using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor firstPassPixels = new PixelAccessor(width, source.Height)) - { - Parallel.For( - 0, - sourceRectangle.Bottom, - this.ParallelOptions, - y => - { - for (int x = minX; x < maxX; x++) - { - // Ensure offsets are normalised for cropping and padding. - Weight[] horizontalValues = this.HorizontalWeights[x - startX].Values; - - // Destination color components - Vector4 destination = Vector4.Zero; - - for (int i = 0; i < horizontalValues.Length; i++) - { - Weight xw = horizontalValues[i]; - destination += sourcePixels[xw.Index + sourceX, y].ToVector4().Expand() * xw.Value; - } - - TColor d = default(TColor); - d.PackFromVector4(destination.Compress()); - firstPassPixels[x, y] = d; - } - }); - - // Now process the rows. - Parallel.For( - minY, - maxY, - this.ParallelOptions, - y => - { - // Ensure offsets are normalised for cropping and padding. - Weight[] verticalValues = this.VerticalWeights[y - startY].Values; - - for (int x = 0; x < width; x++) - { - // Destination color components - Vector4 destination = Vector4.Zero; - - for (int i = 0; i < verticalValues.Length; i++) - { - Weight yw = verticalValues[i]; - destination += firstPassPixels[x, yw.Index + sourceY].ToVector4().Expand() * yw.Value; - } - - TColor d = default(TColor); - d.PackFromVector4(destination.Compress()); - targetPixels[x, y] = d; - } - }); - } - - source.SwapPixelsBuffers(targetPixels); - } - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.Weights.cs b/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.Weights.cs new file mode 100644 index 000000000..24d898fee --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.Weights.cs @@ -0,0 +1,176 @@ +namespace ImageSharp.Processing.Processors +{ + using System; + using System.Numerics; + using System.Runtime.CompilerServices; + + /// + /// Conains the definition of and . + /// + internal abstract partial class ResamplingWeightedProcessor + { + /// + /// Points to a collection of of weights allocated in . + /// + internal unsafe struct WeightsWindow + { + /// + /// The local left index position + /// + public int Left; + + /// + /// The span of weights pointing to . + /// + // TODO: In the case of switching to official System.Memory and System.Buffers.Primitives this should be System.Buffers.Buffer (formerly Memory), because Span is stack-only! + // see: https://github.com/dotnet/corefxlab/blob/873d35ebed7264e2f9adb556f3b61bebc12395d6/docs/specs/memory.md + public BufferSpan Span; + + /// + /// Initializes a new instance of the struct. + /// + /// The local left index + /// The span + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal WeightsWindow(int left, BufferSpan span) + { + this.Left = left; + this.Span = span; + } + + /// + /// Gets an unsafe float* pointer to the beginning of . + /// + public float* Ptr => (float*)this.Span.PointerAtOffset; + + /// + /// Gets the lenghth of the weights window + /// + public int Length => this.Span.Length; + + /// + /// Computes the sum of vectors in 'rowSpan' weighted by weight values, pointed by this instance. + /// + /// The input span of vectors + /// The weighted sum + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector4 ComputeWeightedRowSum(BufferSpan rowSpan) + { + float* horizontalValues = this.Ptr; + int left = this.Left; + Vector4* vecPtr = (Vector4*)rowSpan.PointerAtOffset; + vecPtr += left; + + // Destination color components + Vector4 result = Vector4.Zero; + + for (int i = 0; i < this.Length; i++) + { + float weight = horizontalValues[i]; + result += (*vecPtr) * weight; + vecPtr++; + } + + return result; + } + + /// + /// Computes the sum of vectors in 'rowSpan' weighted by weight values, pointed by this instance. + /// Applies to all input vectors. + /// + /// The input span of vectors + /// The weighted sum + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector4 ComputeExpandedWeightedRowSum(BufferSpan rowSpan) + { + float* horizontalValues = this.Ptr; + int left = this.Left; + Vector4* vecPtr = (Vector4*)rowSpan.PointerAtOffset; + vecPtr += left; + + // Destination color components + Vector4 result = Vector4.Zero; + + for (int i = 0; i < this.Length; i++) + { + float weight = horizontalValues[i]; + result += (*vecPtr).Expand() * weight; + vecPtr++; + } + + return result; + } + + /// + /// Computes the sum of vectors in 'firstPassPixels' at a column pointed by 'x', + /// weighted by weight values, pointed by this instance. + /// + /// The buffer of input vectors in row first order + /// The column position + /// The weighted sum + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector4 ComputeWeightedColumnSum(PinnedImageBuffer firstPassPixels, int x) + { + float* verticalValues = this.Ptr; + int left = this.Left; + + // Destination color components + Vector4 result = Vector4.Zero; + + for (int i = 0; i < this.Length; i++) + { + float yw = verticalValues[i]; + int index = left + i; + result += firstPassPixels[x, index] * yw; + } + + return result; + } + } + + /// + /// Holds the values in an optimized contigous memory region. + /// + internal class WeightsBuffer : IDisposable + { + private PinnedImageBuffer dataBuffer; + + /// + /// Initializes a new instance of the class. + /// + /// The size of the source window + /// The size of the destination window + public WeightsBuffer(int sourceSize, int destinationSize) + { + this.dataBuffer = PinnedImageBuffer.CreateClean(sourceSize, destinationSize); + this.Weights = new WeightsWindow[destinationSize]; + } + + /// + /// Gets the calculated values. + /// + public WeightsWindow[] Weights { get; } + + /// + /// Disposes instance releasing it's backing buffer. + /// + public void Dispose() + { + this.dataBuffer.Dispose(); + } + + /// + /// Slices a weights value at the given positions. + /// + /// The index in destination buffer + /// The local left index value + /// The local right index value + /// The weights + public WeightsWindow GetWeightsWindow(int destIdx, int leftIdx, int rightIdx) + { + BufferSpan span = this.dataBuffer.GetRowSpan(destIdx).Slice(leftIdx, rightIdx - leftIdx); + return new WeightsWindow(leftIdx, span); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.cs index 2d6de4154..255124a75 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.cs @@ -6,13 +6,15 @@ namespace ImageSharp.Processing.Processors { using System; + using System.Buffers; + using System.Runtime.InteropServices; /// /// Provides methods that allow the resizing of images using various algorithms. /// Adapted from /// /// The pixel format. - internal abstract class ResamplingWeightedProcessor : ImageProcessor + internal abstract partial class ResamplingWeightedProcessor : ImageProcessor where TColor : struct, IPixel { /// @@ -59,32 +61,21 @@ namespace ImageSharp.Processing.Processors /// /// Gets or sets the horizontal weights. /// - protected Weights[] HorizontalWeights { get; set; } + protected WeightsBuffer HorizontalWeights { get; set; } /// /// Gets or sets the vertical weights. /// - protected Weights[] VerticalWeights { get; set; } - - /// - protected override void BeforeApply(ImageBase source, Rectangle sourceRectangle) - { - if (!(this.Sampler is NearestNeighborResampler)) - { - this.HorizontalWeights = this.PrecomputeWeights(this.ResizeRectangle.Width, sourceRectangle.Width); - this.VerticalWeights = this.PrecomputeWeights(this.ResizeRectangle.Height, sourceRectangle.Height); - } - } + protected WeightsBuffer VerticalWeights { get; set; } /// /// Computes the weights to apply at each pixel when resizing. /// - /// The destination section size. - /// The source section size. - /// - /// The . - /// - protected Weights[] PrecomputeWeights(int destinationSize, int sourceSize) + /// The destination size + /// The source size + /// The + // TODO: Made internal to simplify experimenting with weights data. Make it protected again when finished figuring out how to optimize all the stuff! + internal unsafe WeightsBuffer PrecomputeWeights(int destinationSize, int sourceSize) { float ratio = (float)sourceSize / destinationSize; float scale = ratio; @@ -96,7 +87,7 @@ namespace ImageSharp.Processing.Processors IResampler sampler = this.Sampler; float radius = (float)Math.Ceiling(scale * sampler.Radius); - Weights[] result = new Weights[destinationSize]; + WeightsBuffer result = new WeightsBuffer(sourceSize, destinationSize); for (int i = 0; i < destinationSize; i++) { @@ -116,67 +107,55 @@ namespace ImageSharp.Processing.Processors } float sum = 0; - result[i] = new Weights(); - Weight[] weights = new Weight[right - left + 1]; + + WeightsWindow ws = result.GetWeightsWindow(i, left, right); + result.Weights[i] = ws; + + float* weights = ws.Ptr; for (int j = left; j <= right; j++) { float weight = sampler.GetValue((j - center) / scale); sum += weight; - weights[j - left] = new Weight(j, weight); + weights[j - left] = weight; } // Normalise, best to do it here rather than in the pixel loop later on. if (sum > 0) { - for (int w = 0; w < weights.Length; w++) + for (int w = 0; w < ws.Length; w++) { - weights[w].Value = weights[w].Value / sum; + weights[w] = weights[w] / sum; } } - - result[i].Values = weights; } return result; } - /// - /// Represents the weight to be added to a scaled pixel. - /// - protected struct Weight + /// + protected override void BeforeApply(ImageBase source, Rectangle sourceRectangle) { - /// - /// Initializes a new instance of the struct. - /// - /// The index. - /// The value. - public Weight(int index, float value) + if (!(this.Sampler is NearestNeighborResampler)) { - this.Index = index; - this.Value = value; - } - - /// - /// Gets the pixel index. - /// - public int Index { get; } + this.HorizontalWeights = this.PrecomputeWeights( + this.ResizeRectangle.Width, + sourceRectangle.Width); - /// - /// Gets or sets the result of the interpolation algorithm. - /// - public float Value { get; set; } + this.VerticalWeights = this.PrecomputeWeights( + this.ResizeRectangle.Height, + sourceRectangle.Height); + } } - /// - /// Represents a collection of weights and their sum. - /// - protected class Weights + /// + protected override void AfterApply(ImageBase source, Rectangle sourceRectangle) { - /// - /// Gets or sets the values. - /// - public Weight[] Values { get; set; } + base.AfterApply(source, sourceRectangle); + this.HorizontalWeights?.Dispose(); + this.HorizontalWeights = null; + this.VerticalWeights?.Dispose(); + this.VerticalWeights = null; } } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs index a43745a05..944e245ac 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs @@ -12,9 +12,6 @@ namespace ImageSharp.Processing.Processors /// /// Provides methods that allow the resizing of images using various algorithms. /// - /// - /// This version and the have been separated out to improve performance. - /// /// The pixel format. internal class ResizeProcessor : ResamplingWeightedProcessor where TColor : struct, IPixel @@ -45,7 +42,7 @@ namespace ImageSharp.Processing.Processors } /// - protected override void OnApply(ImageBase source, Rectangle sourceRectangle) + protected override unsafe void OnApply(ImageBase source, Rectangle sourceRectangle) { // Jump out, we'll deal with that later. if (source.Width == this.Width && source.Height == this.Height && sourceRectangle == this.ResizeRectangle) @@ -104,36 +101,49 @@ namespace ImageSharp.Processing.Processors // A 2-pass 1D algorithm appears to be faster than splitting a 1-pass 2D algorithm // First process the columns. Since we are not using multiple threads startY and endY // are the upper and lower bounds of the source rectangle. + + // TODO: Using a transposed variant of 'firstPassPixels' could eliminate the need for the WeightsWindow.ComputeWeightedColumnSum() method, and improve speed! using (PixelAccessor targetPixels = new PixelAccessor(width, height)) { using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor firstPassPixels = new PixelAccessor(width, source.Height)) + using (PinnedImageBuffer firstPassPixels = new PinnedImageBuffer(width, source.Height)) { + firstPassPixels.Clear(); + Parallel.For( 0, sourceRectangle.Bottom, this.ParallelOptions, y => - { - for (int x = minX; x < maxX; x++) { - // Ensure offsets are normalised for cropping and padding. - Weight[] horizontalValues = this.HorizontalWeights[x - startX].Values; - - // Destination color components - Vector4 destination = Vector4.Zero; - - for (int i = 0; i < horizontalValues.Length; i++) + // TODO: Without Parallel.For() this buffer object could be reused: + using (PinnedBuffer tempRowBuffer = new PinnedBuffer(sourcePixels.Width)) { - Weight xw = horizontalValues[i]; - destination += sourcePixels[xw.Index + sourceX, y].ToVector4() * xw.Value; + BufferSpan sourceRow = sourcePixels.GetRowSpan(y); + + BulkPixelOperations.Instance.ToVector4( + sourceRow, + tempRowBuffer, + sourceRow.Length); + + if (this.Compand) + { + for (int x = minX; x < maxX; x++) + { + WeightsWindow window = this.HorizontalWeights.Weights[x - startX]; + firstPassPixels[x, y] = window.ComputeExpandedWeightedRowSum(tempRowBuffer); + } + } + else + { + for (int x = minX; x < maxX; x++) + { + WeightsWindow window = this.HorizontalWeights.Weights[x - startX]; + firstPassPixels[x, y] = window.ComputeWeightedRowSum(tempRowBuffer); + } + } } - - TColor d = default(TColor); - d.PackFromVector4(destination); - firstPassPixels[x, y] = d; - } - }); + }); // Now process the rows. Parallel.For( @@ -143,22 +153,31 @@ namespace ImageSharp.Processing.Processors y => { // Ensure offsets are normalised for cropping and padding. - Weight[] verticalValues = this.VerticalWeights[y - startY].Values; + WeightsWindow window = this.VerticalWeights.Weights[y - startY]; - for (int x = 0; x < width; x++) + if (this.Compand) { - // Destination color components - Vector4 destination = Vector4.Zero; - - for (int i = 0; i < verticalValues.Length; i++) + for (int x = 0; x < width; x++) { - Weight yw = verticalValues[i]; - destination += firstPassPixels[x, yw.Index + sourceY].ToVector4() * yw.Value; + // Destination color components + Vector4 destination = window.ComputeWeightedColumnSum(firstPassPixels, x); + destination = destination.Compress(); + TColor d = default(TColor); + d.PackFromVector4(destination); + targetPixels[x, y] = d; } + } + else + { + for (int x = 0; x < width; x++) + { + // Destination color components + Vector4 destination = window.ComputeWeightedColumnSum(firstPassPixels, x); - TColor d = default(TColor); - d.PackFromVector4(destination); - targetPixels[x, y] = d; + TColor d = default(TColor); + d.PackFromVector4(destination); + targetPixels[x, y] = d; + } } }); } diff --git a/src/ImageSharp/Processing/Transforms/Resize.cs b/src/ImageSharp/Processing/Transforms/Resize.cs index ab256c4ae..1952aa1a7 100644 --- a/src/ImageSharp/Processing/Transforms/Resize.cs +++ b/src/ImageSharp/Processing/Transforms/Resize.cs @@ -156,16 +156,8 @@ namespace ImageSharp Guard.MustBeGreaterThan(width, 0, nameof(width)); Guard.MustBeGreaterThan(height, 0, nameof(height)); - ResamplingWeightedProcessor processor; - - if (compand) - { - processor = new CompandingResizeProcessor(sampler, width, height, targetRectangle); - } - else - { - processor = new ResizeProcessor(sampler, width, height, targetRectangle); - } + ResizeProcessor processor = + new ResizeProcessor(sampler, width, height, targetRectangle) { Compand = compand }; source.ApplyProcessor(processor, sourceRectangle); return source; diff --git a/tests/ImageSharp.Sandbox46/Program.cs b/tests/ImageSharp.Sandbox46/Program.cs index 467663a53..dad603523 100644 --- a/tests/ImageSharp.Sandbox46/Program.cs +++ b/tests/ImageSharp.Sandbox46/Program.cs @@ -38,12 +38,19 @@ namespace ImageSharp.Sandbox46 public static void Main(string[] args) { // RunDecodeJpegProfilingTests(); + // RunToVector4ProfilingTest(); - RunToVector4ProfilingTest(); + RunResizeProfilingTest(); Console.ReadLine(); } + private static void RunResizeProfilingTest() + { + ResizeProfilingBenchmarks test = new ResizeProfilingBenchmarks(new ConsoleOutput()); + test.ResizeBicubic(2000, 2000); + } + private static void RunToVector4ProfilingTest() { BulkPixelOperationsTests.Color tests = new BulkPixelOperationsTests.Color(new ConsoleOutput()); diff --git a/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs b/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs index fa950c4cf..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 BufferPointer Source => this.SourceBuffer.Slice(); - public BufferPointer ActualDest => this.ActualDestBuffer.Slice(); + public BufferSpan Source => this.SourceBuffer; + public BufferSpan ActualDest => this.ActualDestBuffer; public TestBuffers(TSource[] source, TDest[] expectedDest) { @@ -335,7 +335,7 @@ namespace ImageSharp.Tests.Colors public void Verify() { - int count = this.ExpectedDestBuffer.Count; + int count = this.ExpectedDestBuffer.Length; if (typeof(TDest) == typeof(Vector4)) { @@ -364,7 +364,7 @@ namespace ImageSharp.Tests.Colors internal static void TestOperation( TSource[] source, TDest[] expected, - Action, BufferPointer> action) + Action, BufferSpan> action) where TSource : struct where TDest : struct { diff --git a/tests/ImageSharp.Tests/Common/BufferPointerTests.cs b/tests/ImageSharp.Tests/Common/BufferSpanTests.cs similarity index 59% rename from tests/ImageSharp.Tests/Common/BufferPointerTests.cs rename to tests/ImageSharp.Tests/Common/BufferSpanTests.cs index c82b63f11..aee032acc 100644 --- a/tests/ImageSharp.Tests/Common/BufferPointerTests.cs +++ b/tests/ImageSharp.Tests/Common/BufferSpanTests.cs @@ -7,131 +7,133 @@ namespace ImageSharp.Tests.Common using Xunit; - public unsafe class BufferPointerTests + using static TestStructs; + + public unsafe class BufferSpanTests { - public struct Foo + [Fact] + public void AsBytes() { - public int A; - - public double B; + Foo[] fooz = { new Foo(1, 2), new Foo(3, 4), new Foo(5, 6) }; - public Foo(int a, double b) + using (PinnedBuffer colorBuf = new PinnedBuffer(fooz)) { - this.A = a; - this.B = b; - } + BufferSpan orig = colorBuf.Slice(1); + BufferSpan asBytes = (BufferSpan < byte > )orig; - internal static Foo[] CreateArray(int size) - { - Foo[] result = new Foo[size]; - for (int i = 0; i < size; i++) - { - result[i] = new Foo(i+1, i+1); - } - return result; + Assert.Equal(asBytes.Start, sizeof(Foo)); + Assert.Equal(orig.PointerAtOffset, asBytes.PointerAtOffset); } } - /// - /// sizeof(AlignedFoo) == sizeof(long) - /// - public struct AlignedFoo + public class Construct { - public int A; - - public int B; - - static AlignedFoo() + [Fact] + public void Basic() { - Assert.Equal(sizeof(AlignedFoo), sizeof(long)); - } + Foo[] array = Foo.CreateArray(3); + fixed (Foo* p = array) + { + // Act: + BufferSpan span = new BufferSpan(array, p); - public AlignedFoo(int a, int b) - { - this.A = a; - this.B = b; + // Assert: + Assert.Equal(array, span.Array); + Assert.Equal((IntPtr)p, span.PointerAtOffset); + Assert.Equal(3, span.Length); + } } - internal static AlignedFoo[] CreateArray(int size) + [Fact] + public void WithStart() { - AlignedFoo[] result = new AlignedFoo[size]; - for (int i = 0; i < size; i++) + Foo[] array = Foo.CreateArray(4); + int start = 2; + fixed (Foo* p = array) { - result[i] = new AlignedFoo(i + 1, i + 1); + // Act: + BufferSpan span = new BufferSpan(array, p, start); + + // Assert: + Assert.Equal(array, span.Array); + Assert.Equal(start, span.Start); + Assert.Equal((IntPtr)(p + start), span.PointerAtOffset); + Assert.Equal(array.Length - start, span.Length); } - return result; } - } - - [Fact] - public void AsBytes() - { - Foo[] fooz = { new Foo(1, 2), new Foo(3, 4), new Foo(5, 6) }; - using (PinnedBuffer colorBuf = new PinnedBuffer(fooz)) + [Fact] + public void WithStartAndLength() { - BufferPointer orig = colorBuf.Slice(1); - BufferPointer asBytes = (BufferPointer < byte > )orig; - - Assert.Equal(asBytes.Offset, sizeof(Foo)); - Assert.Equal(orig.PointerAtOffset, asBytes.PointerAtOffset); + Foo[] array = Foo.CreateArray(10); + int start = 2; + int length = 3; + fixed (Foo* p = array) + { + // Act: + BufferSpan span = new BufferSpan(array, p, start, length); + + // Assert: + Assert.Equal(array, span.Array); + Assert.Equal(start, span.Start); + Assert.Equal((IntPtr)(p + start), span.PointerAtOffset); + Assert.Equal(length, span.Length); + } } } - - [Fact] - public void ConstructWithoutOffset() + + public class Slice { - Foo[] array = Foo.CreateArray(3); - fixed (Foo* p = array) + [Fact] + public void StartOnly() { - // Act: - BufferPointer ap = new BufferPointer(array, p); + Foo[] array = Foo.CreateArray(5); + int start0 = 2; + int start1 = 2; + int totalOffset = start0 + start1; - // Assert: - Assert.Equal(array, ap.Array); - Assert.Equal((IntPtr)p, ap.PointerAtOffset); - } - } + fixed (Foo* p = array) + { + BufferSpan span = new BufferSpan(array, p, start0); - [Fact] - public void ConstructWithOffset() - { - Foo[] array = Foo.CreateArray(3); - int offset = 2; - fixed (Foo* p = array) - { - // Act: - BufferPointer ap = new BufferPointer(array, p, offset); + // Act: + span = span.Slice(start1); - // Assert: - Assert.Equal(array, ap.Array); - Assert.Equal(offset, ap.Offset); - Assert.Equal((IntPtr)(p+offset), ap.PointerAtOffset); + // Assert: + Assert.Equal(array, span.Array); + Assert.Equal(totalOffset, span.Start); + Assert.Equal((IntPtr)(p + totalOffset), span.PointerAtOffset); + Assert.Equal(array.Length - totalOffset, span.Length); + } } - } - [Fact] - public void Slice() - { - Foo[] array = Foo.CreateArray(5); - int offset0 = 2; - int offset1 = 2; - int totalOffset = offset0 + offset1; - fixed (Foo* p = array) + [Fact] + public void StartAndLength() { - BufferPointer ap = new BufferPointer(array, p, offset0); + Foo[] array = Foo.CreateArray(10); + int start0 = 2; + int start1 = 2; + int totalOffset = start0 + start1; + int sliceLength = 3; - // Act: - ap = ap.Slice(offset1); + fixed (Foo* p = array) + { + BufferSpan span = new BufferSpan(array, p, start0); - // Assert: - Assert.Equal(array, ap.Array); - Assert.Equal(totalOffset, ap.Offset); - Assert.Equal((IntPtr)(p + totalOffset), ap.PointerAtOffset); + // Act: + span = span.Slice(start1, sliceLength); + + // Assert: + Assert.Equal(array, span.Array); + Assert.Equal(totalOffset, span.Start); + Assert.Equal((IntPtr)(p + totalOffset), span.PointerAtOffset); + Assert.Equal(sliceLength, span.Length); + } } } + [Theory] [InlineData(4)] [InlineData(1500)] @@ -142,7 +144,7 @@ namespace ImageSharp.Tests.Common int offset = 2; fixed (Foo* p = array) { - BufferPointer ap = new BufferPointer(array, p, offset); + BufferSpan ap = new BufferSpan(array, p, offset); // Act: ap.Clear(count); @@ -155,6 +157,51 @@ namespace ImageSharp.Tests.Common } + public class Indexer + { + public static readonly TheoryData IndexerData = + new TheoryData() + { + { 10, 0, 0 }, + { 10, 2, 0 }, + { 16, 0, 3 }, + { 16, 2, 3 }, + { 10, 0, 9 }, + { 10, 1, 8 } + }; + + [Theory] + [MemberData(nameof(IndexerData))] + public void Read(int length, int start, int index) + { + Foo[] a = Foo.CreateArray(length); + fixed (Foo* p = a) + { + BufferSpan span = new BufferSpan(a, p, start); + + Foo element = span[index]; + + Assert.Equal(a[start + index], element); + } + } + + [Theory] + [MemberData(nameof(IndexerData))] + public void Write(int length, int start, int index) + { + Foo[] a = Foo.CreateArray(length); + fixed (Foo* p = a) + { + BufferSpan span = new BufferSpan(a, p, start); + + span[index] = new Foo(666, 666); + + Assert.Equal(new Foo(666, 666), a[start + index]); + } + } + } + + public class Copy { private static void AssertNotDefault(T[] data, int idx) @@ -194,10 +241,10 @@ namespace ImageSharp.Tests.Common fixed (Foo* pSource = source) fixed (Foo* pDest = dest) { - BufferPointer apSource = new BufferPointer(source, pSource, 1); - BufferPointer apDest = new BufferPointer(dest, pDest, 1); + BufferSpan apSource = new BufferSpan(source, pSource, 1); + BufferSpan apDest = new BufferSpan(dest, pDest, 1); - BufferPointer.Copy(apSource, apDest, count-1); + BufferSpan.Copy(apSource, apDest, count-1); } AssertNotDefault(source, 1); @@ -221,10 +268,10 @@ namespace ImageSharp.Tests.Common fixed (AlignedFoo* pSource = source) fixed (AlignedFoo* pDest = dest) { - BufferPointer apSource = new BufferPointer(source, pSource, 1); - BufferPointer apDest = new BufferPointer(dest, pDest, 1); + BufferSpan apSource = new BufferSpan(source, pSource, 1); + BufferSpan apDest = new BufferSpan(dest, pDest, 1); - BufferPointer.Copy(apSource, apDest, count - 1); + BufferSpan.Copy(apSource, apDest, count - 1); } AssertNotDefault(source, 1); @@ -248,10 +295,10 @@ namespace ImageSharp.Tests.Common fixed (int* pSource = source) fixed (int* pDest = dest) { - BufferPointer apSource = new BufferPointer(source, pSource, 1); - BufferPointer apDest = new BufferPointer(dest, pDest, 1); + BufferSpan apSource = new BufferSpan(source, pSource, 1); + BufferSpan apDest = new BufferSpan(dest, pDest, 1); - BufferPointer.Copy(apSource, apDest, count -1); + BufferSpan.Copy(apSource, apDest, count -1); } AssertNotDefault(source, 1); @@ -276,10 +323,10 @@ namespace ImageSharp.Tests.Common fixed (Foo* pSource = source) fixed (byte* pDest = dest) { - BufferPointer apSource = new BufferPointer(source, pSource, 1); - BufferPointer apDest = new BufferPointer(dest, pDest, sizeof(Foo)); + BufferSpan apSource = new BufferSpan(source, pSource, 1); + BufferSpan apDest = new BufferSpan(dest, pDest, sizeof(Foo)); - BufferPointer.Copy(apSource, apDest, count - 1); + BufferSpan.Copy(apSource, apDest, count - 1); } AssertNotDefault(source, 1); @@ -303,10 +350,10 @@ namespace ImageSharp.Tests.Common fixed (AlignedFoo* pSource = source) fixed (byte* pDest = dest) { - BufferPointer apSource = new BufferPointer(source, pSource, 1); - BufferPointer apDest = new BufferPointer(dest, pDest, sizeof(AlignedFoo)); + BufferSpan apSource = new BufferSpan(source, pSource, 1); + BufferSpan apDest = new BufferSpan(dest, pDest, sizeof(AlignedFoo)); - BufferPointer.Copy(apSource, apDest, count - 1); + BufferSpan.Copy(apSource, apDest, count - 1); } AssertNotDefault(source, 1); @@ -330,10 +377,10 @@ namespace ImageSharp.Tests.Common fixed (int* pSource = source) fixed (byte* pDest = dest) { - BufferPointer apSource = new BufferPointer(source, pSource); - BufferPointer apDest = new BufferPointer(dest, pDest); + BufferSpan apSource = new BufferSpan(source, pSource); + BufferSpan apDest = new BufferSpan(dest, pDest); - BufferPointer.Copy(apSource, apDest, count); + BufferSpan.Copy(apSource, apDest, count); } AssertNotDefault(source, 1); @@ -355,10 +402,10 @@ namespace ImageSharp.Tests.Common fixed(byte* pSource = source) fixed (Foo* pDest = dest) { - BufferPointer apSource = new BufferPointer(source, pSource); - BufferPointer apDest = new BufferPointer(dest, pDest); + BufferSpan apSource = new BufferSpan(source, pSource); + BufferSpan apDest = new BufferSpan(dest, pDest); - BufferPointer.Copy(apSource, apDest, count); + BufferSpan.Copy(apSource, apDest, count); } AssertNotDefault(source, sizeof(Foo) + 1); @@ -378,17 +425,18 @@ namespace ImageSharp.Tests.Common using (PinnedBuffer colorBuf = new PinnedBuffer(colors)) using (PinnedBuffer byteBuf = new PinnedBuffer(colors.Length*4)) { - BufferPointer.Copy(colorBuf, byteBuf, colorBuf.Count); + BufferSpan.Copy(colorBuf, byteBuf, colorBuf.Length); byte[] a = byteBuf.Array; - for (int i = 0; i < byteBuf.Count; i++) + for (int i = 0; i < byteBuf.Length; i++) { Assert.Equal((byte)i, a[i]); } } } + internal static bool ElementsAreEqual(Foo[] array, byte[] rawArray, int index) { fixed (Foo* pArray = array) diff --git a/tests/ImageSharp.Tests/Common/PinnedBufferTests.cs b/tests/ImageSharp.Tests/Common/PinnedBufferTests.cs index d47bd5b94..5e812d5a0 100644 --- a/tests/ImageSharp.Tests/Common/PinnedBufferTests.cs +++ b/tests/ImageSharp.Tests/Common/PinnedBufferTests.cs @@ -7,15 +7,10 @@ using Xunit; + using static TestStructs; + public unsafe class PinnedBufferTests { - public struct Foo - { - public int A; - - public double B; - } - [Theory] [InlineData(42)] [InlineData(1111)] @@ -25,7 +20,7 @@ { Assert.False(buffer.IsDisposedOrLostArrayOwnership); Assert.NotNull(buffer.Array); - Assert.Equal(count, buffer.Count); + Assert.Equal(count, buffer.Length); Assert.True(buffer.Array.Length >= count); VerifyPointer(buffer); @@ -42,7 +37,7 @@ { Assert.False(buffer.IsDisposedOrLostArrayOwnership); Assert.Equal(array, buffer.Array); - Assert.Equal(count, buffer.Count); + Assert.Equal(count, buffer.Length); VerifyPointer(buffer); } @@ -66,18 +61,56 @@ [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.Count; j++) - { - Assert.Equal(0, buffer.Array[j]); - buffer.Array[j] = 666; - } - } - }); + Assert.Equal(0, buffer.Array[j]); + buffer.Array[j] = 666; + } + } + } + } + + public class Indexer + { + public static readonly TheoryData IndexerData = + new TheoryData() + { + { 10, 0 }, + { 16, 3 }, + { 10, 9 } + }; + + [Theory] + [MemberData(nameof(IndexerData))] + public void Read(int length, int index) + { + Foo[] a = Foo.CreateArray(length); + + using (PinnedBuffer buffer = new PinnedBuffer(a)) + { + Foo element = buffer[index]; + + Assert.Equal(a[index], element); + } + } + + [Theory] + [MemberData(nameof(IndexerData))] + public void Write(int length, int index) + { + Foo[] a = Foo.CreateArray(length); + + using (PinnedBuffer buffer = new PinnedBuffer(a)) + { + buffer[index] = new Foo(666, 666); + + Assert.Equal(new Foo(666, 666), a[index]); + } + } } [Fact] @@ -89,21 +122,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)) { - BufferPointer arrayPtr = buffer.Slice(); + BufferSpan span = buffer.Span; - Assert.Equal(a, arrayPtr.Array); - Assert.Equal(0, arrayPtr.Offset); - 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..a23f93a70 --- /dev/null +++ b/tests/ImageSharp.Tests/Common/PinnedImageBufferTests.cs @@ -0,0 +1,106 @@ +// ReSharper disable InconsistentNaming +namespace ImageSharp.Tests.Common +{ + using System.Runtime.CompilerServices; + + using Xunit; + + using static TestStructs; + + 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) + { + Foo[] array = new Foo[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(Foo) * 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(Foo) * (width * y + x), span.PointerAtOffset); + } + } + + [Theory] + [InlineData(42, 8, 0, 0)] + [InlineData(400, 1000, 20, 10)] + [InlineData(99, 88, 98, 87)] + public void Indexer(int width, int height, int x, int y) + { + using (PinnedImageBuffer buffer = new PinnedImageBuffer(width, height)) + { + Foo[] array = buffer.Array; + + ref Foo actual = ref buffer[x, y]; + + ref Foo expected = ref array[y * width + x]; + + Assert.True(Unsafe.AreSame(ref expected, ref actual)); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Common/TestStructs.cs b/tests/ImageSharp.Tests/Common/TestStructs.cs new file mode 100644 index 000000000..9e762bbd1 --- /dev/null +++ b/tests/ImageSharp.Tests/Common/TestStructs.cs @@ -0,0 +1,62 @@ +namespace ImageSharp.Tests.Common +{ + using Xunit; + + public static class TestStructs + { + public struct Foo + { + public int A; + + public double B; + + public Foo(int a, double b) + { + this.A = a; + this.B = b; + } + + internal static Foo[] CreateArray(int size) + { + Foo[] result = new Foo[size]; + for (int i = 0; i < size; i++) + { + result[i] = new Foo(i + 1, i + 1); + } + return result; + } + } + + + /// + /// sizeof(AlignedFoo) == sizeof(long) + /// + public unsafe struct AlignedFoo + { + public int A; + + public int B; + + static AlignedFoo() + { + Assert.Equal(sizeof(AlignedFoo), sizeof(long)); + } + + public AlignedFoo(int a, int b) + { + this.A = a; + this.B = b; + } + + internal static AlignedFoo[] CreateArray(int size) + { + AlignedFoo[] result = new AlignedFoo[size]; + for (int i = 0; i < size; i++) + { + result[i] = new AlignedFoo(i + 1, i + 1); + } + return result; + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processors/Filters/ResizeProfilingBenchmarks.cs b/tests/ImageSharp.Tests/Processors/Filters/ResizeProfilingBenchmarks.cs new file mode 100644 index 000000000..da09aa85e --- /dev/null +++ b/tests/ImageSharp.Tests/Processors/Filters/ResizeProfilingBenchmarks.cs @@ -0,0 +1,61 @@ +namespace ImageSharp.Tests +{ + using System.IO; + using System.Text; + + using ImageSharp.Processing; + using ImageSharp.Processing.Processors; + + using Xunit; + using Xunit.Abstractions; + + public class ResizeProfilingBenchmarks : MeasureFixture + { + public ResizeProfilingBenchmarks(ITestOutputHelper output) + : base(output) + { + } + + public int ExecutionCount { get; set; } = 50; + + // [Theory] // Benchmark, enable manually! + [InlineData(100, 100)] + [InlineData(2000, 2000)] + public void ResizeBicubic(int width, int height) + { + this.Measure(this.ExecutionCount, + () => + { + using (Image image = new Image(width, height)) + { + image.Resize(width / 4, height / 4); + } + }); + } + + // [Fact] + public void PrintWeightsData() + { + ResizeProcessor proc = new ResizeProcessor(new BicubicResampler(), 200, 200); + + ResamplingWeightedProcessor.WeightsBuffer weights = proc.PrecomputeWeights(200, 500); + + StringBuilder bld = new StringBuilder(); + + foreach (ResamplingWeightedProcessor.WeightsWindow window in weights.Weights) + { + for (int i = 0; i < window.Length; i++) + { + float value = window.Span[i]; + bld.Append(value); + bld.Append("| "); + } + bld.AppendLine(); + } + + File.WriteAllText("BicubicWeights.MD", bld.ToString()); + + //this.Output.WriteLine(bld.ToString()); + } + } +} \ No newline at end of file