From b2fb41fab509c1154947f48a15ca09352732ab70 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sat, 18 Mar 2017 00:01:31 +0100 Subject: [PATCH 01/12] BufferPointer became BufferSpan --- src/ImageSharp/Colors/Color.BulkOperations.cs | 32 +-- .../BulkPixelOperations{TColor}.cs | 84 +++---- .../Common/Memory/BufferPointer{T}.cs | 143 ------------ .../{BufferPointer.cs => BufferSpan.cs} | 28 +-- src/ImageSharp/Common/Memory/BufferSpan{T}.cs | 209 ++++++++++++++++++ .../Common/Memory/PinnedBuffer{T}.cs | 48 ++-- src/ImageSharp/Image/PixelAccessor{TColor}.cs | 38 ++-- src/ImageSharp/Image/PixelArea{TColor}.cs | 6 +- .../Colors/BulkPixelOperationsTests.cs | 8 +- ...fferPointerTests.cs => BufferSpanTests.cs} | 184 +++++++++------ .../Common/PinnedBufferTests.cs | 10 +- 11 files changed, 455 insertions(+), 335 deletions(-) delete mode 100644 src/ImageSharp/Common/Memory/BufferPointer{T}.cs rename src/ImageSharp/Common/Memory/{BufferPointer.cs => BufferSpan.cs} (81%) create mode 100644 src/ImageSharp/Common/Memory/BufferSpan{T}.cs rename tests/ImageSharp.Tests/Common/{BufferPointerTests.cs => BufferSpanTests.cs} (66%) 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 441f6b8ce..000000000 --- a/src/ImageSharp/Common/Memory/BufferPointer{T}.cs +++ /dev/null @@ -1,143 +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; } - - /// - /// 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..0507173e9 --- /dev/null +++ b/src/ImageSharp/Common/Memory/BufferSpan{T}.cs @@ -0,0 +1,209 @@ +// +// 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; } + + /// + /// 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 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, BufferSpan.USizeOf(count)); + } + else + { + System.Array.Clear(this.Array, this.Start, count); + } + } + + [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/PinnedBuffer{T}.cs b/src/ImageSharp/Common/Memory/PinnedBuffer{T}.cs index d002e08fb..7378a8d64 100644 --- a/src/ImageSharp/Common/Memory/PinnedBuffer{T}.cs +++ b/src/ImageSharp/Common/Memory/PinnedBuffer{T}.cs @@ -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 count of "relevant" elements in 'array'. /// The array to pin. - public PinnedBuffer(int count, T[] array) + public PinnedBuffer(int length, T[] array) { - 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,11 +100,11 @@ namespace ImageSharp public IntPtr Pointer { get; private set; } /// - /// Converts to an . + /// Converts to an . /// /// The to convert. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static implicit operator BufferPointer(PinnedBuffer buffer) + public static implicit operator BufferSpan(PinnedBuffer buffer) { return buffer.Slice(); } @@ -122,24 +122,24 @@ namespace ImageSharp } /// - /// Gets a to the beginning of the raw data of the buffer. + /// Gets a to the beginning of the raw data of the buffer. /// - /// The + /// The [MethodImpl(MethodImplOptions.AggressiveInlining)] - public unsafe BufferPointer Slice() + public unsafe BufferSpan Slice() { - return new BufferPointer(this.Array, (void*)this.Pointer); + return new BufferSpan(this.Array, (void*)this.Pointer); } /// - /// Gets a to an offseted position inside the buffer. + /// Gets a to an offseted position inside the buffer. /// /// The offset - /// The + /// The [MethodImpl(MethodImplOptions.AggressiveInlining)] - public unsafe BufferPointer Slice(int offset) + public unsafe BufferSpan Slice(int offset) { - return new BufferPointer(this.Array, (void*)this.Pointer, offset); + return new BufferSpan(this.Array, (void*)this.Pointer, offset); } /// @@ -163,7 +163,7 @@ namespace ImageSharp this.isPoolingOwner = false; this.Array = null; - this.Count = 0; + this.Length = 0; GC.SuppressFinalize(this); } @@ -190,12 +190,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); + this.Slice().Clear(this.Length); } /// diff --git a/src/ImageSharp/Image/PixelAccessor{TColor}.cs b/src/ImageSharp/Image/PixelAccessor{TColor}.cs index 25e232cf8..d22deec74 100644 --- a/src/ImageSharp/Image/PixelAccessor{TColor}.cs +++ b/src/ImageSharp/Image/PixelAccessor{TColor}.cs @@ -239,12 +239,12 @@ namespace ImageSharp } /// - /// Gets a to the row 'y' beginning from the pixel at 'x'. + /// 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) + /// The + internal BufferSpan GetRowPointer(int x, int y) { return this.pixelBuffer.Slice((y * this.Width) + x); } @@ -288,8 +288,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.GetRowPointer(y); + BufferSpan destination = this.GetRowPointer(targetX, targetY + y); Operations.PackFromZyxBytes(source, destination, width); } @@ -308,8 +308,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.GetRowPointer(y); + BufferSpan destination = this.GetRowPointer(targetX, targetY + y); Operations.PackFromZyxwBytes(source, destination, width); } @@ -328,8 +328,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.GetRowPointer(y); + BufferSpan destination = this.GetRowPointer(targetX, targetY + y); Operations.PackFromXyzBytes(source, destination, width); } @@ -348,8 +348,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.GetRowPointer(y); + BufferSpan destination = this.GetRowPointer(targetX, targetY + y); Operations.PackFromXyzwBytes(source, destination, width); } } @@ -367,8 +367,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.GetRowPointer(sourceX, sourceY + y); + BufferSpan destination = area.GetRowPointer(y); Operations.ToZyxBytes(source, destination, width); } } @@ -386,8 +386,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.GetRowPointer(sourceX, sourceY + y); + BufferSpan destination = area.GetRowPointer(y); Operations.ToZyxwBytes(source, destination, width); } } @@ -405,8 +405,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.GetRowPointer(sourceX, sourceY + y); + BufferSpan destination = area.GetRowPointer(y); Operations.ToXyzBytes(source, destination, width); } } @@ -424,8 +424,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.GetRowPointer(sourceX, sourceY + y); + BufferSpan destination = area.GetRowPointer(y); Operations.ToXyzwBytes(source, destination, width); } } diff --git a/src/ImageSharp/Image/PixelArea{TColor}.cs b/src/ImageSharp/Image/PixelArea{TColor}.cs index 8f2fa5b7a..0a3c5710c 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 GetRowPointer(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 fa950c4cf..141c9d888 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.Slice(); + public BufferSpan ActualDest => this.ActualDestBuffer.Slice(); 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 66% rename from tests/ImageSharp.Tests/Common/BufferPointerTests.cs rename to tests/ImageSharp.Tests/Common/BufferSpanTests.cs index c82b63f11..f524bdb43 100644 --- a/tests/ImageSharp.Tests/Common/BufferPointerTests.cs +++ b/tests/ImageSharp.Tests/Common/BufferSpanTests.cs @@ -7,7 +7,7 @@ namespace ImageSharp.Tests.Common using Xunit; - public unsafe class BufferPointerTests + public unsafe class BufferSpanTests { public struct Foo { @@ -70,68 +70,122 @@ namespace ImageSharp.Tests.Common using (PinnedBuffer colorBuf = new PinnedBuffer(fooz)) { - BufferPointer orig = colorBuf.Slice(1); - BufferPointer asBytes = (BufferPointer < byte > )orig; + BufferSpan orig = colorBuf.Slice(1); + BufferSpan asBytes = (BufferSpan < byte > )orig; - Assert.Equal(asBytes.Offset, sizeof(Foo)); + Assert.Equal(asBytes.Start, sizeof(Foo)); Assert.Equal(orig.PointerAtOffset, asBytes.PointerAtOffset); } } - - [Fact] - public void ConstructWithoutOffset() + + public class Construct { - Foo[] array = Foo.CreateArray(3); - fixed (Foo* p = array) + [Fact] + public void Basic() { - // Act: - BufferPointer ap = new BufferPointer(array, p); + Foo[] array = Foo.CreateArray(3); + fixed (Foo* p = array) + { + // Act: + BufferSpan span = new BufferSpan(array, p); - // Assert: - Assert.Equal(array, ap.Array); - Assert.Equal((IntPtr)p, ap.PointerAtOffset); + // Assert: + Assert.Equal(array, span.Array); + Assert.Equal((IntPtr)p, span.PointerAtOffset); + Assert.Equal(3, span.Length); + } } - } - [Fact] - public void ConstructWithOffset() - { - Foo[] array = Foo.CreateArray(3); - int offset = 2; - fixed (Foo* p = array) + [Fact] + public void WithStart() { - // Act: - BufferPointer ap = new BufferPointer(array, p, offset); + Foo[] array = Foo.CreateArray(4); + int start = 2; + fixed (Foo* p = array) + { + // 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); + } + } - // Assert: - Assert.Equal(array, ap.Array); - Assert.Equal(offset, ap.Offset); - Assert.Equal((IntPtr)(p+offset), ap.PointerAtOffset); + [Fact] + public void WithStartAndLength() + { + 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 Slice() + public class Slice { - Foo[] array = Foo.CreateArray(5); - int offset0 = 2; - int offset1 = 2; - int totalOffset = offset0 + offset1; - fixed (Foo* p = array) + [Fact] + public void StartOnly() { - BufferPointer ap = new BufferPointer(array, p, offset0); + Foo[] array = Foo.CreateArray(5); + int start0 = 2; + int start1 = 2; + int totalOffset = start0 + start1; - // Act: - ap = ap.Slice(offset1); + fixed (Foo* p = array) + { + BufferSpan span = new BufferSpan(array, p, start0); + + // Act: + span = span.Slice(start1); - // Assert: - Assert.Equal(array, ap.Array); - Assert.Equal(totalOffset, ap.Offset); - Assert.Equal((IntPtr)(p + totalOffset), 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 StartAndLength() + { + Foo[] array = Foo.CreateArray(10); + int start0 = 2; + int start1 = 2; + int totalOffset = start0 + start1; + int sliceLength = 3; + + fixed (Foo* p = array) + { + BufferSpan span = new BufferSpan(array, p, start0); + + // 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 +196,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); @@ -194,10 +248,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 +275,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 +302,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 +330,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 +357,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 +384,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 +409,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,11 +432,11 @@ 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]); } diff --git a/tests/ImageSharp.Tests/Common/PinnedBufferTests.cs b/tests/ImageSharp.Tests/Common/PinnedBufferTests.cs index d47bd5b94..9eb12a536 100644 --- a/tests/ImageSharp.Tests/Common/PinnedBufferTests.cs +++ b/tests/ImageSharp.Tests/Common/PinnedBufferTests.cs @@ -25,7 +25,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 +42,7 @@ { Assert.False(buffer.IsDisposedOrLostArrayOwnership); Assert.Equal(array, buffer.Array); - Assert.Equal(count, buffer.Count); + Assert.Equal(count, buffer.Length); VerifyPointer(buffer); } @@ -71,7 +71,7 @@ { using (PinnedBuffer buffer = PinnedBuffer.CreateClean(42)) { - for (int j = 0; j < buffer.Count; j++) + for (int j = 0; j < buffer.Length; j++) { Assert.Equal(0, buffer.Array[j]); buffer.Array[j] = 666; @@ -96,10 +96,10 @@ using (PinnedBuffer buffer = new PinnedBuffer(a)) { - BufferPointer arrayPtr = buffer.Slice(); + BufferSpan arrayPtr = buffer.Slice(); Assert.Equal(a, arrayPtr.Array); - Assert.Equal(0, arrayPtr.Offset); + Assert.Equal(0, arrayPtr.Start); Assert.Equal(buffer.Pointer, arrayPtr.PointerAtOffset); } } From fa40b262169977d3348e201e020fa4c37f8320da Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sat, 18 Mar 2017 02:10:37 +0100 Subject: [PATCH 02/12] introduced PinnedImageBuffer --- src/ImageSharp/Common/Memory/BufferSpan{T}.cs | 13 ++- .../Common/Memory/IPinnedImageBuffer{T}.cs | 31 +++++++ .../Common/Memory/PinnedBuffer{T}.cs | 32 ++++--- .../Memory/PinnedImageBufferExtensions.cs | 45 ++++++++++ .../Common/Memory/PinnedImageBuffer{T}.cs | 62 +++++++++++++ src/ImageSharp/Image/PixelAccessor{TColor}.cs | 51 ++++++----- src/ImageSharp/Image/PixelArea{TColor}.cs | 2 +- .../Colors/BulkPixelOperationsTests.cs | 4 +- .../Common/PinnedBufferTests.cs | 88 +++++++++++++++---- .../Common/PinnedImageBufferTests.cs | 86 ++++++++++++++++++ 10 files changed, 355 insertions(+), 59 deletions(-) create mode 100644 src/ImageSharp/Common/Memory/IPinnedImageBuffer{T}.cs create mode 100644 src/ImageSharp/Common/Memory/PinnedImageBufferExtensions.cs create mode 100644 src/ImageSharp/Common/Memory/PinnedImageBuffer{T}.cs create mode 100644 tests/ImageSharp.Tests/Common/PinnedImageBufferTests.cs 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 From e2d780107cd9c2f4ff1a360d5e9c627ae3458436 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sat, 18 Mar 2017 14:24:06 +0100 Subject: [PATCH 03/12] BufferSpan ref indexer + Fix Drawing usages --- .../Brushes/ImageBrush{TColor}.cs | 2 +- .../Brushes/PatternBrush{TColor}.cs | 2 +- .../Brushes/Processors/BrushApplicator.cs | 2 +- .../Brushes/RecolorBrush{TColor}.cs | 2 +- .../Brushes/SolidBrush{TColor}.cs | 2 +- src/ImageSharp/Common/Memory/BufferSpan{T}.cs | 17 +++++++ .../Common/BufferSpanTests.cs | 46 +++++++++++++++++++ 7 files changed, 68 insertions(+), 5 deletions(-) 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/Common/Memory/BufferSpan{T}.cs b/src/ImageSharp/Common/Memory/BufferSpan{T}.cs index f3cb85884..8ef88814c 100644 --- a/src/ImageSharp/Common/Memory/BufferSpan{T}.cs +++ b/src/ImageSharp/Common/Memory/BufferSpan{T}.cs @@ -106,6 +106,23 @@ namespace ImageSharp /// 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 /// diff --git a/tests/ImageSharp.Tests/Common/BufferSpanTests.cs b/tests/ImageSharp.Tests/Common/BufferSpanTests.cs index f524bdb43..36cfe1b2a 100644 --- a/tests/ImageSharp.Tests/Common/BufferSpanTests.cs +++ b/tests/ImageSharp.Tests/Common/BufferSpanTests.cs @@ -209,6 +209,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) @@ -443,6 +488,7 @@ namespace ImageSharp.Tests.Common } } + internal static bool ElementsAreEqual(Foo[] array, byte[] rawArray, int index) { fixed (Foo* pArray = array) From 8b91c02a274f833dfea9bd5dad577e86019970ad Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sat, 18 Mar 2017 16:57:15 +0100 Subject: [PATCH 04/12] PinnedBuffer and PinnedImageBuffer indexers --- .../Common/Memory/PinnedBuffer{T}.cs | 17 +++++ .../Common/Memory/PinnedImageBuffer{T}.cs | 9 +++ src/ImageSharp/Image/PixelAccessor{TColor}.cs | 20 +++--- .../Common/BufferSpanTests.cs | 56 +---------------- .../Common/PinnedBufferTests.cs | 48 +++++++++++--- .../Common/PinnedImageBufferTests.cs | 34 +++++++--- tests/ImageSharp.Tests/Common/TestStructs.cs | 62 +++++++++++++++++++ 7 files changed, 168 insertions(+), 78 deletions(-) create mode 100644 tests/ImageSharp.Tests/Common/TestStructs.cs diff --git a/src/ImageSharp/Common/Memory/PinnedBuffer{T}.cs b/src/ImageSharp/Common/Memory/PinnedBuffer{T}.cs index d58354a71..611688c99 100644 --- a/src/ImageSharp/Common/Memory/PinnedBuffer{T}.cs +++ b/src/ImageSharp/Common/Memory/PinnedBuffer{T}.cs @@ -104,6 +104,23 @@ namespace ImageSharp /// 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 . /// diff --git a/src/ImageSharp/Common/Memory/PinnedImageBuffer{T}.cs b/src/ImageSharp/Common/Memory/PinnedImageBuffer{T}.cs index 6ec0fc4e5..380545d70 100644 --- a/src/ImageSharp/Common/Memory/PinnedImageBuffer{T}.cs +++ b/src/ImageSharp/Common/Memory/PinnedImageBuffer{T}.cs @@ -6,6 +6,7 @@ namespace ImageSharp { using System; + using System.Runtime.CompilerServices; /// /// Represents a pinned buffer of value type objects @@ -46,6 +47,14 @@ namespace ImageSharp /// 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] => ref this.Array[(this.Width * y) + x]; + /// /// Creates a clean instance of initializing it's elements with 'default(T)'. /// diff --git a/src/ImageSharp/Image/PixelAccessor{TColor}.cs b/src/ImageSharp/Image/PixelAccessor{TColor}.cs index e21e3aa46..cea058951 100644 --- a/src/ImageSharp/Image/PixelAccessor{TColor}.cs +++ b/src/ImageSharp/Image/PixelAccessor{TColor}.cs @@ -241,16 +241,16 @@ 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 BufferSpan GetRowSpan(int x, int y) - { - return this.pixelBuffer.Slice((y * this.Width) + x, this.Width - x); - } + ///// + ///// Gets a to the row 'y' beginning from the pixel at 'x'. + ///// + ///// The x coordinate + ///// The y coordinate + ///// The + //internal BufferSpan GetRowSpan(int x, int y) + //{ + // return this.pixelBuffer.Slice((y * this.Width) + x, this.Width - x); + //} /// /// Sets the pixel buffer in an unsafe manner. This should not be used unless you know what its doing!!! diff --git a/tests/ImageSharp.Tests/Common/BufferSpanTests.cs b/tests/ImageSharp.Tests/Common/BufferSpanTests.cs index 36cfe1b2a..aee032acc 100644 --- a/tests/ImageSharp.Tests/Common/BufferSpanTests.cs +++ b/tests/ImageSharp.Tests/Common/BufferSpanTests.cs @@ -7,62 +7,10 @@ namespace ImageSharp.Tests.Common using Xunit; + using static TestStructs; + public unsafe class BufferSpanTests { - 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 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; - } - } - [Fact] public void AsBytes() { diff --git a/tests/ImageSharp.Tests/Common/PinnedBufferTests.cs b/tests/ImageSharp.Tests/Common/PinnedBufferTests.cs index f7189aadf..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)] @@ -79,6 +74,45 @@ } } + 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] public void Dispose() { diff --git a/tests/ImageSharp.Tests/Common/PinnedImageBufferTests.cs b/tests/ImageSharp.Tests/Common/PinnedImageBufferTests.cs index fa9730798..7756b0aa9 100644 --- a/tests/ImageSharp.Tests/Common/PinnedImageBufferTests.cs +++ b/tests/ImageSharp.Tests/Common/PinnedImageBufferTests.cs @@ -5,6 +5,8 @@ namespace ImageSharp.Tests.Common using Xunit; + using static TestStructs; + public unsafe class PinnedImageBufferTests { [Theory] @@ -12,7 +14,7 @@ namespace ImageSharp.Tests.Common [InlineData(1025, 17)] public void Construct(int width, int height) { - using (PinnedImageBuffer buffer = new PinnedImageBuffer(width, height)) + using (PinnedImageBuffer buffer = new PinnedImageBuffer(width, height)) { Assert.Equal(width, buffer.Width); Assert.Equal(height, buffer.Height); @@ -25,8 +27,8 @@ namespace ImageSharp.Tests.Common [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)) + 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); @@ -57,9 +59,9 @@ namespace ImageSharp.Tests.Common [InlineData(17, 42, 41)] public void GetRowSpanY(int width, int height, int y) { - using (PinnedImageBuffer buffer = new PinnedImageBuffer(width, height)) + using (PinnedImageBuffer buffer = new PinnedImageBuffer(width, height)) { - BufferSpan span = buffer.GetRowSpan(y); + BufferSpan span = buffer.GetRowSpan(y); Assert.Equal(width * y, span.Start); Assert.Equal(width, span.Length); @@ -73,14 +75,32 @@ namespace ImageSharp.Tests.Common [InlineData(17, 42, 0, 41)] public void GetRowSpanXY(int width, int height, int x, int y) { - using (PinnedImageBuffer buffer = new PinnedImageBuffer(width, height)) + using (PinnedImageBuffer buffer = new PinnedImageBuffer(width, height)) { - BufferSpan span = buffer.GetRowSpan(x, y); + 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); } } + + [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 From 378c2348861e15e91bb4851a0383459f1e02bc1e Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sat, 18 Mar 2017 18:17:54 +0100 Subject: [PATCH 05/12] refactor ResamplingWeightedProcessor.Weights --- src/ImageSharp/Image/PixelAccessor{TColor}.cs | 11 -- .../Transforms/CompandingResizeProcessor.cs | 24 ++-- .../Transforms/ResamplingWeightedProcessor.cs | 117 +++++++++++------- .../Processors/Transforms/ResizeProcessor.cs | 63 ++++++---- 4 files changed, 127 insertions(+), 88 deletions(-) diff --git a/src/ImageSharp/Image/PixelAccessor{TColor}.cs b/src/ImageSharp/Image/PixelAccessor{TColor}.cs index cea058951..f5393cfb3 100644 --- a/src/ImageSharp/Image/PixelAccessor{TColor}.cs +++ b/src/ImageSharp/Image/PixelAccessor{TColor}.cs @@ -241,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 BufferSpan GetRowSpan(int x, int y) - //{ - // return this.pixelBuffer.Slice((y * this.Width) + x, this.Width - x); - //} - /// /// Sets the pixel buffer in an unsafe manner. This should not be used unless you know what its doing!!! /// diff --git a/src/ImageSharp/Processing/Processors/Transforms/CompandingResizeProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/CompandingResizeProcessor.cs index f5314d448..14cea480d 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/CompandingResizeProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/CompandingResizeProcessor.cs @@ -46,7 +46,7 @@ namespace ImageSharp.Processing.Processors public override bool Compand { get; set; } = true; /// - 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) @@ -119,15 +119,18 @@ namespace ImageSharp.Processing.Processors for (int x = minX; x < maxX; x++) { // Ensure offsets are normalised for cropping and padding. - Weight[] horizontalValues = this.HorizontalWeights[x - startX].Values; + Weights ws = this.HorizontalWeights.Weights[x - startX]; + float* horizontalValues = ws.Ptr; + int left = ws.Left; // Destination color components Vector4 destination = Vector4.Zero; - for (int i = 0; i < horizontalValues.Length; i++) + for (int i = 0; i < ws.Length; i++) { - Weight xw = horizontalValues[i]; - destination += sourcePixels[xw.Index + sourceX, y].ToVector4().Expand() * xw.Value; + float xw = horizontalValues[i]; + int index = left + i; + destination += sourcePixels[index, y].ToVector4().Expand() * xw; } TColor d = default(TColor); @@ -144,17 +147,20 @@ namespace ImageSharp.Processing.Processors y => { // Ensure offsets are normalised for cropping and padding. - Weight[] verticalValues = this.VerticalWeights[y - startY].Values; + Weights ws = this.VerticalWeights.Weights[y - startY]; + float* verticalValues = ws.Ptr; + int left = ws.Left; for (int x = 0; x < width; x++) { // Destination color components Vector4 destination = Vector4.Zero; - for (int i = 0; i < verticalValues.Length; i++) + for (int i = 0; i < ws.Length; i++) { - Weight yw = verticalValues[i]; - destination += firstPassPixels[x, yw.Index + sourceY].ToVector4().Expand() * yw.Value; + float yw = verticalValues[i]; + int index = left + i; + destination += firstPassPixels[x, index].ToVector4().Expand() * yw; } TColor d = default(TColor); diff --git a/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.cs index 2d6de4154..51f382df2 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.cs @@ -6,6 +6,9 @@ namespace ImageSharp.Processing.Processors { using System; + using System.Buffers; + using System.Runtime.CompilerServices; + using System.Runtime.InteropServices; /// /// Provides methods that allow the resizing of images using various algorithms. @@ -59,32 +62,41 @@ namespace ImageSharp.Processing.Processors /// /// Gets or sets the horizontal weights. /// - protected Weights[] HorizontalWeights { get; set; } + protected Weights.Buffer HorizontalWeights { get; set; } /// /// Gets or sets the vertical weights. /// - protected Weights[] VerticalWeights { get; set; } + protected Weights.Buffer 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); + this.HorizontalWeights = this.PrecomputeWeights( + this.ResizeRectangle.Width, + sourceRectangle.Width); + + this.VerticalWeights = this.PrecomputeWeights( + this.ResizeRectangle.Height, + sourceRectangle.Height); } } + protected override void AfterApply(ImageBase source, Rectangle sourceRectangle) + { + base.AfterApply(source, sourceRectangle); + this.HorizontalWeights?.Dispose(); + this.HorizontalWeights = null; + this.VerticalWeights?.Dispose(); + this.VerticalWeights = null; + } + /// /// 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) + protected unsafe Weights.Buffer PrecomputeWeights(int destinationSize, int sourceSize) { float ratio = (float)sourceSize / destinationSize; float scale = ratio; @@ -96,8 +108,8 @@ namespace ImageSharp.Processing.Processors IResampler sampler = this.Sampler; float radius = (float)Math.Ceiling(scale * sampler.Radius); - Weights[] result = new Weights[destinationSize]; - + Weights.Buffer result = new Weights.Buffer(sourceSize, destinationSize); + for (int i = 0; i < destinationSize; i++) { float center = ((i + .5F) * ratio) - .5F; @@ -116,67 +128,82 @@ namespace ImageSharp.Processing.Processors } float sum = 0; - result[i] = new Weights(); - Weight[] weights = new Weight[right - left + 1]; + + result.Weights[i] = result.Slice(i, left, right); + Weights ws = result.Weights[i]; + + 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. + /// Represents a collection of weights and their sum. /// - protected struct Weight + protected unsafe struct Weights { /// - /// Initializes a new instance of the struct. + /// The local left index position /// - /// The index. - /// The value. - public Weight(int index, float value) + public int Left; + + public BufferSpan Span; + + public float* Ptr => (float*)Span.PointerAtOffset; + + public int Length => Span.Length; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private Weights(int left, BufferSpan span) { - this.Index = index; - this.Value = value; + this.Left = left; + this.Span = span; } - /// - /// Gets the pixel index. - /// - public int Index { get; } + internal unsafe class Buffer : IDisposable + { + private PinnedImageBuffer dataBuffer; - /// - /// Gets or sets the result of the interpolation algorithm. - /// - public float Value { get; set; } - } + public Weights[] Weights { get; } - /// - /// Represents a collection of weights and their sum. - /// - protected class Weights - { - /// - /// Gets or sets the values. - /// - public Weight[] Values { get; set; } + public float* DataPtr { get; } + + internal Weights Slice(int i, int leftIdx, int rightIdx) + { + var span = dataBuffer.GetRowSpan(i).Slice(leftIdx, rightIdx - leftIdx); + return new Weights(leftIdx, span); + } + + public Buffer(int sourceSize, int destinationSize) + { + this.dataBuffer = new PinnedImageBuffer(sourceSize, destinationSize); + this.dataBuffer.Clear(); + this.DataPtr = (float*)this.dataBuffer.Pointer; + this.Weights = new Weights[destinationSize]; + } + + public void Dispose() + { + this.dataBuffer.Dispose(); + } + } } } } \ 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..39b739134 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs @@ -45,7 +45,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) @@ -107,33 +107,47 @@ namespace ImageSharp.Processing.Processors 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); + + for (int x = minX; x < maxX; x++) + { + // Ensure offsets are normalised for cropping and padding. + + Weights ws = this.HorizontalWeights.Weights[x - startX]; + float* horizontalValues = ws.Ptr; + int left = ws.Left; + + // Destination color components + Vector4 destination = Vector4.Zero; + + for (int i = 0; i < ws.Length; i++) + { + float xw = horizontalValues[i]; + int index = left + i; + destination += tempRowBuffer[index] * xw; + } + + firstPassPixels[x, y] = destination; + } } - - TColor d = default(TColor); - d.PackFromVector4(destination); - firstPassPixels[x, y] = d; - } - }); + }); // Now process the rows. Parallel.For( @@ -143,17 +157,20 @@ namespace ImageSharp.Processing.Processors y => { // Ensure offsets are normalised for cropping and padding. - Weight[] verticalValues = this.VerticalWeights[y - startY].Values; + Weights ws = this.VerticalWeights.Weights[y - startY]; + float* verticalValues = ws.Ptr; + int left = ws.Left; for (int x = 0; x < width; x++) { // Destination color components Vector4 destination = Vector4.Zero; - for (int i = 0; i < verticalValues.Length; i++) + for (int i = 0; i < ws.Length; i++) { - Weight yw = verticalValues[i]; - destination += firstPassPixels[x, yw.Index + sourceY].ToVector4() * yw.Value; + float yw = verticalValues[i]; + int index = left + i; + destination += firstPassPixels[x, index] * yw; } TColor d = default(TColor); From c523077d0aa3c52aae8859702a354ea7397621de Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sat, 18 Mar 2017 19:08:00 +0100 Subject: [PATCH 06/12] more refactor on Weights stuff --- .../Transforms/CompandingResizeProcessor.cs | 4 +- .../ResamplingWeightedProcessor.Weights.cs | 144 ++++++++++++++++++ .../Transforms/ResamplingWeightedProcessor.cs | 72 ++------- .../Processors/Transforms/ResizeProcessor.cs | 34 +---- 4 files changed, 164 insertions(+), 90 deletions(-) create mode 100644 src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.Weights.cs diff --git a/src/ImageSharp/Processing/Processors/Transforms/CompandingResizeProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/CompandingResizeProcessor.cs index 14cea480d..c8a43b3d9 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/CompandingResizeProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/CompandingResizeProcessor.cs @@ -119,7 +119,7 @@ namespace ImageSharp.Processing.Processors for (int x = minX; x < maxX; x++) { // Ensure offsets are normalised for cropping and padding. - Weights ws = this.HorizontalWeights.Weights[x - startX]; + WeightsWindow ws = this.HorizontalWeights.Weights[x - startX]; float* horizontalValues = ws.Ptr; int left = ws.Left; @@ -147,7 +147,7 @@ namespace ImageSharp.Processing.Processors y => { // Ensure offsets are normalised for cropping and padding. - Weights ws = this.VerticalWeights.Weights[y - startY]; + WeightsWindow ws = this.VerticalWeights.Weights[y - startY]; float* verticalValues = ws.Ptr; int left = ws.Left; 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..00a81100d --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.Weights.cs @@ -0,0 +1,144 @@ +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 . + /// + protected unsafe struct WeightsWindow + { + /// + /// The local left index position + /// + public int Left; + + /// + /// The span of weights pointing to . + /// + 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 + public Vector4 ComputeWeightedRowSum(BufferSpan rowSpan) + { + float* horizontalValues = this.Ptr; + int left = this.Left; + + // Destination color components + Vector4 result = Vector4.Zero; + + for (int i = 0; i < this.Length; i++) + { + float xw = horizontalValues[i]; + int index = left + i; + result += rowSpan[index] * xw; + } + + 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 + 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. + /// + protected 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 = new PinnedImageBuffer(sourceSize, destinationSize); + this.dataBuffer.Clear(); + 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 51f382df2..f74bf7edd 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.cs @@ -7,7 +7,6 @@ namespace ImageSharp.Processing.Processors { using System; using System.Buffers; - using System.Runtime.CompilerServices; using System.Runtime.InteropServices; /// @@ -15,7 +14,7 @@ namespace ImageSharp.Processing.Processors /// Adapted from /// /// The pixel format. - internal abstract class ResamplingWeightedProcessor : ImageProcessor + internal abstract partial class ResamplingWeightedProcessor : ImageProcessor where TColor : struct, IPixel { /// @@ -62,12 +61,12 @@ namespace ImageSharp.Processing.Processors /// /// Gets or sets the horizontal weights. /// - protected Weights.Buffer HorizontalWeights { get; set; } + protected WeightsBuffer HorizontalWeights { get; set; } /// /// Gets or sets the vertical weights. /// - protected Weights.Buffer VerticalWeights { get; set; } + protected WeightsBuffer VerticalWeights { get; set; } /// protected override void BeforeApply(ImageBase source, Rectangle sourceRectangle) @@ -84,6 +83,7 @@ namespace ImageSharp.Processing.Processors } } + /// protected override void AfterApply(ImageBase source, Rectangle sourceRectangle) { base.AfterApply(source, sourceRectangle); @@ -96,7 +96,10 @@ namespace ImageSharp.Processing.Processors /// /// Computes the weights to apply at each pixel when resizing. /// - protected unsafe Weights.Buffer PrecomputeWeights(int destinationSize, int sourceSize) + /// The destination size + /// The source size + /// The + protected unsafe WeightsBuffer PrecomputeWeights(int destinationSize, int sourceSize) { float ratio = (float)sourceSize / destinationSize; float scale = ratio; @@ -108,8 +111,8 @@ namespace ImageSharp.Processing.Processors IResampler sampler = this.Sampler; float radius = (float)Math.Ceiling(scale * sampler.Radius); - Weights.Buffer result = new Weights.Buffer(sourceSize, destinationSize); - + WeightsBuffer result = new WeightsBuffer(sourceSize, destinationSize); + for (int i = 0; i < destinationSize; i++) { float center = ((i + .5F) * ratio) - .5F; @@ -129,8 +132,8 @@ namespace ImageSharp.Processing.Processors float sum = 0; - result.Weights[i] = result.Slice(i, left, right); - Weights ws = result.Weights[i]; + WeightsWindow ws = result.GetWeightsWindow(i, left, right); + result.Weights[i] = ws; float* weights = ws.Ptr; @@ -154,56 +157,5 @@ namespace ImageSharp.Processing.Processors return result; } - /// - /// Represents a collection of weights and their sum. - /// - protected unsafe struct Weights - { - /// - /// The local left index position - /// - public int Left; - - public BufferSpan Span; - - public float* Ptr => (float*)Span.PointerAtOffset; - - public int Length => Span.Length; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private Weights(int left, BufferSpan span) - { - this.Left = left; - this.Span = span; - } - - internal unsafe class Buffer : IDisposable - { - private PinnedImageBuffer dataBuffer; - - public Weights[] Weights { get; } - - public float* DataPtr { get; } - - internal Weights Slice(int i, int leftIdx, int rightIdx) - { - var span = dataBuffer.GetRowSpan(i).Slice(leftIdx, rightIdx - leftIdx); - return new Weights(leftIdx, span); - } - - public Buffer(int sourceSize, int destinationSize) - { - this.dataBuffer = new PinnedImageBuffer(sourceSize, destinationSize); - this.dataBuffer.Clear(); - this.DataPtr = (float*)this.dataBuffer.Pointer; - this.Weights = new Weights[destinationSize]; - } - - public void Dispose() - { - this.dataBuffer.Dispose(); - } - } - } } } \ 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 39b739134..77b3f3b6f 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs @@ -104,6 +104,8 @@ 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()) @@ -128,23 +130,8 @@ namespace ImageSharp.Processing.Processors for (int x = minX; x < maxX; x++) { - // Ensure offsets are normalised for cropping and padding. - - Weights ws = this.HorizontalWeights.Weights[x - startX]; - float* horizontalValues = ws.Ptr; - int left = ws.Left; - - // Destination color components - Vector4 destination = Vector4.Zero; - - for (int i = 0; i < ws.Length; i++) - { - float xw = horizontalValues[i]; - int index = left + i; - destination += tempRowBuffer[index] * xw; - } - - firstPassPixels[x, y] = destination; + WeightsWindow window = this.HorizontalWeights.Weights[x - startX]; + firstPassPixels[x, y] = window.ComputeWeightedRowSum(tempRowBuffer); } } }); @@ -157,21 +144,12 @@ namespace ImageSharp.Processing.Processors y => { // Ensure offsets are normalised for cropping and padding. - Weights ws = this.VerticalWeights.Weights[y - startY]; - float* verticalValues = ws.Ptr; - int left = ws.Left; + WeightsWindow window = this.VerticalWeights.Weights[y - startY]; for (int x = 0; x < width; x++) { // Destination color components - Vector4 destination = Vector4.Zero; - - for (int i = 0; i < ws.Length; i++) - { - float yw = verticalValues[i]; - int index = left + i; - destination += firstPassPixels[x, index] * yw; - } + Vector4 destination = window.ComputeWeightedColumnSum(firstPassPixels, x); TColor d = default(TColor); d.PackFromVector4(destination); From a0a67ddf3591424251c14f7b40bc530eb806dc52 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sat, 18 Mar 2017 19:47:22 +0100 Subject: [PATCH 07/12] merged ResizeProcessor and CompandingResizeProcessor --- .../Transforms/CompandingResizeProcessor.cs | 177 ------------------ .../ResamplingWeightedProcessor.Weights.cs | 24 +++ .../Transforms/ResamplingWeightedProcessor.cs | 1 - .../Processors/Transforms/ResizeProcessor.cs | 48 +++-- .../Processing/Transforms/Resize.cs | 12 +- .../Processors/Filters/ResizeTests.cs | 26 +++ 6 files changed, 88 insertions(+), 200 deletions(-) delete mode 100644 src/ImageSharp/Processing/Processors/Transforms/CompandingResizeProcessor.cs diff --git a/src/ImageSharp/Processing/Processors/Transforms/CompandingResizeProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/CompandingResizeProcessor.cs deleted file mode 100644 index c8a43b3d9..000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/CompandingResizeProcessor.cs +++ /dev/null @@ -1,177 +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 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) - { - 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. - WeightsWindow ws = this.HorizontalWeights.Weights[x - startX]; - float* horizontalValues = ws.Ptr; - int left = ws.Left; - - // Destination color components - Vector4 destination = Vector4.Zero; - - for (int i = 0; i < ws.Length; i++) - { - float xw = horizontalValues[i]; - int index = left + i; - destination += sourcePixels[index, y].ToVector4().Expand() * xw; - } - - 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. - WeightsWindow ws = this.VerticalWeights.Weights[y - startY]; - float* verticalValues = ws.Ptr; - int left = ws.Left; - - for (int x = 0; x < width; x++) - { - // Destination color components - Vector4 destination = Vector4.Zero; - - for (int i = 0; i < ws.Length; i++) - { - float yw = verticalValues[i]; - int index = left + i; - destination += firstPassPixels[x, index].ToVector4().Expand() * yw; - } - - 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 index 00a81100d..c7386487a 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.Weights.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.Weights.cs @@ -69,6 +69,30 @@ namespace ImageSharp.Processing.Processors 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 + public Vector4 ComputeExpandedWeightedRowSum(BufferSpan rowSpan) + { + float* horizontalValues = this.Ptr; + int left = this.Left; + + // Destination color components + Vector4 result = Vector4.Zero; + + for (int i = 0; i < this.Length; i++) + { + float xw = horizontalValues[i]; + int index = left + i; + result += rowSpan[index].Expand() * xw; + } + + return result; + } + /// /// Computes the sum of vectors in 'firstPassPixels' at a column pointed by 'x', /// weighted by weight values, pointed by this instance. diff --git a/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.cs index f74bf7edd..d1a709384 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.cs @@ -156,6 +156,5 @@ namespace ImageSharp.Processing.Processors return result; } - } } \ 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 77b3f3b6f..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 @@ -123,15 +120,27 @@ namespace ImageSharp.Processing.Processors using (PinnedBuffer tempRowBuffer = new PinnedBuffer(sourcePixels.Width)) { BufferSpan sourceRow = sourcePixels.GetRowSpan(y); + BulkPixelOperations.Instance.ToVector4( sourceRow, tempRowBuffer, sourceRow.Length); - for (int x = minX; x < maxX; x++) + if (this.Compand) + { + for (int x = minX; x < maxX; x++) + { + WeightsWindow window = this.HorizontalWeights.Weights[x - startX]; + firstPassPixels[x, y] = window.ComputeExpandedWeightedRowSum(tempRowBuffer); + } + } + else { - WeightsWindow window = this.HorizontalWeights.Weights[x - startX]; - firstPassPixels[x, y] = window.ComputeWeightedRowSum(tempRowBuffer); + for (int x = minX; x < maxX; x++) + { + WeightsWindow window = this.HorizontalWeights.Weights[x - startX]; + firstPassPixels[x, y] = window.ComputeWeightedRowSum(tempRowBuffer); + } } } }); @@ -146,14 +155,29 @@ namespace ImageSharp.Processing.Processors // Ensure offsets are normalised for cropping and padding. WeightsWindow window = this.VerticalWeights.Weights[y - startY]; - for (int x = 0; x < width; x++) + if (this.Compand) { - // Destination color components - Vector4 destination = window.ComputeWeightedColumnSum(firstPassPixels, x); + for (int x = 0; x < width; x++) + { + // 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.Tests/Processors/Filters/ResizeTests.cs b/tests/ImageSharp.Tests/Processors/Filters/ResizeTests.cs index 643033f4c..35994e028 100644 --- a/tests/ImageSharp.Tests/Processors/Filters/ResizeTests.cs +++ b/tests/ImageSharp.Tests/Processors/Filters/ResizeTests.cs @@ -8,6 +8,32 @@ namespace ImageSharp.Tests using System.IO; using Processing; using Xunit; + using Xunit.Abstractions; + + public class ResizeProfilingBenchmarks : MeasureFixture + { + public ResizeProfilingBenchmarks(ITestOutputHelper output) + : base(output) + { + } + + public int ExecutionCount { get; set; } = 50; + + [Theory] + [InlineData(100, 100)] + [InlineData(1000, 1000)] + public void ResizeBicubic(int width, int height) + { + this.Measure(this.ExecutionCount, + () => + { + using (Image image = new Image(width, height)) + { + image.Resize(width / 4, height / 4); + } + }); + } + } public class ResizeTests : FileTestBase { From cd4fb809cecc84b4f3f91af365677e2644925ebb Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sat, 18 Mar 2017 20:16:17 +0100 Subject: [PATCH 08/12] improved, sequential ResizeProcessor --- .../Common/Memory/PinnedImageBuffer{T}.cs | 9 +- .../ResamplingWeightedProcessor.Weights.cs | 19 +++- .../Processors/Transforms/ResizeProcessor.cs | 104 ++++++++---------- tests/ImageSharp.Sandbox46/Program.cs | 9 +- .../Processors/Filters/ResizeTests.cs | 2 +- 5 files changed, 76 insertions(+), 67 deletions(-) diff --git a/src/ImageSharp/Common/Memory/PinnedImageBuffer{T}.cs b/src/ImageSharp/Common/Memory/PinnedImageBuffer{T}.cs index 380545d70..3ff174c5d 100644 --- a/src/ImageSharp/Common/Memory/PinnedImageBuffer{T}.cs +++ b/src/ImageSharp/Common/Memory/PinnedImageBuffer{T}.cs @@ -53,7 +53,14 @@ namespace ImageSharp /// The x coordinate (row) /// The y coordinate (position at row) /// A reference to the element. - public ref T this[int x, int y] => ref this.Array[(this.Width * y) + x]; + 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)'. diff --git a/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.Weights.cs b/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.Weights.cs index c7386487a..785a1f20c 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.Weights.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.Weights.cs @@ -51,19 +51,22 @@ namespace ImageSharp.Processing.Processors /// /// 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 xw = horizontalValues[i]; - int index = left + i; - result += rowSpan[index] * xw; + float weight = horizontalValues[i]; + result += (*vecPtr) * weight; + vecPtr++; } return result; @@ -75,19 +78,22 @@ namespace ImageSharp.Processing.Processors /// /// 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 xw = horizontalValues[i]; - int index = left + i; - result += rowSpan[index].Expand() * xw; + float weight = horizontalValues[i]; + result += (*vecPtr).Expand() * weight; + vecPtr++; } return result; @@ -100,6 +106,7 @@ namespace ImageSharp.Processing.Processors /// 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; diff --git a/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs index 944e245ac..0e1a215e0 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs @@ -110,76 +110,64 @@ namespace ImageSharp.Processing.Processors { firstPassPixels.Clear(); - Parallel.For( - 0, - sourceRectangle.Bottom, - this.ParallelOptions, - y => - { - // TODO: Without Parallel.For() this buffer object could be reused: - using (PinnedBuffer tempRowBuffer = new PinnedBuffer(sourcePixels.Width)) - { - 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); - } - } - } - }); - - // Now process the rows. - Parallel.For( - minY, - maxY, - this.ParallelOptions, - y => + using (PinnedBuffer tempRowBuffer = new PinnedBuffer(sourcePixels.Width)) + { + for (int y = 0; y < sourceRectangle.Bottom; y++) { - // Ensure offsets are normalised for cropping and padding. - WeightsWindow window = this.VerticalWeights.Weights[y - startY]; + BufferSpan sourceRow = sourcePixels.GetRowSpan(y); + + BulkPixelOperations.Instance.ToVector4(sourceRow, tempRowBuffer, sourceRow.Length); if (this.Compand) { - for (int x = 0; x < width; x++) + for (int x = minX; x < maxX; x++) { - // Destination color components - Vector4 destination = window.ComputeWeightedColumnSum(firstPassPixels, x); - destination = destination.Compress(); - TColor d = default(TColor); - d.PackFromVector4(destination); - targetPixels[x, y] = d; + WeightsWindow window = this.HorizontalWeights.Weights[x - startX]; + firstPassPixels[x, y] = window.ComputeExpandedWeightedRowSum(tempRowBuffer); } } else { - for (int x = 0; x < width; x++) + for (int x = minX; x < maxX; x++) { - // Destination color components - Vector4 destination = window.ComputeWeightedColumnSum(firstPassPixels, x); - - TColor d = default(TColor); - d.PackFromVector4(destination); - targetPixels[x, y] = d; + WeightsWindow window = this.HorizontalWeights.Weights[x - startX]; + firstPassPixels[x, y] = window.ComputeWeightedRowSum(tempRowBuffer); } } - }); + } + } + + // Now process the rows. + for (int y = minY; y < maxY; y++) + { + // Ensure offsets are normalised for cropping and padding. + WeightsWindow window = this.VerticalWeights.Weights[y - startY]; + + if (this.Compand) + { + for (int x = 0; x < width; x++) + { + // 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; + } + } + } } source.SwapPixelsBuffers(targetPixels); 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/Processors/Filters/ResizeTests.cs b/tests/ImageSharp.Tests/Processors/Filters/ResizeTests.cs index 35994e028..a95b196b7 100644 --- a/tests/ImageSharp.Tests/Processors/Filters/ResizeTests.cs +++ b/tests/ImageSharp.Tests/Processors/Filters/ResizeTests.cs @@ -21,7 +21,7 @@ namespace ImageSharp.Tests [Theory] [InlineData(100, 100)] - [InlineData(1000, 1000)] + [InlineData(2000, 2000)] public void ResizeBicubic(int width, int height) { this.Measure(this.ExecutionCount, From 7ced8f07c6fa8a468fb052f34a340d3d125d09de Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sat, 18 Mar 2017 20:27:27 +0100 Subject: [PATCH 09/12] make it parallel again! --- .../Processors/Transforms/ResizeProcessor.cs | 104 ++++++++++-------- 1 file changed, 58 insertions(+), 46 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs index 0e1a215e0..944e245ac 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs @@ -110,64 +110,76 @@ namespace ImageSharp.Processing.Processors { firstPassPixels.Clear(); - using (PinnedBuffer tempRowBuffer = new PinnedBuffer(sourcePixels.Width)) - { - for (int y = 0; y < sourceRectangle.Bottom; y++) - { - BufferSpan sourceRow = sourcePixels.GetRowSpan(y); + Parallel.For( + 0, + sourceRectangle.Bottom, + this.ParallelOptions, + y => + { + // TODO: Without Parallel.For() this buffer object could be reused: + using (PinnedBuffer tempRowBuffer = new PinnedBuffer(sourcePixels.Width)) + { + 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); + } + } + } + }); - BulkPixelOperations.Instance.ToVector4(sourceRow, tempRowBuffer, sourceRow.Length); + // Now process the rows. + Parallel.For( + minY, + maxY, + this.ParallelOptions, + y => + { + // Ensure offsets are normalised for cropping and padding. + WeightsWindow window = this.VerticalWeights.Weights[y - startY]; if (this.Compand) { - for (int x = minX; x < maxX; x++) + for (int x = 0; x < width; x++) { - WeightsWindow window = this.HorizontalWeights.Weights[x - startX]; - firstPassPixels[x, y] = window.ComputeExpandedWeightedRowSum(tempRowBuffer); + // 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 = minX; x < maxX; x++) + for (int x = 0; x < width; x++) { - WeightsWindow window = this.HorizontalWeights.Weights[x - startX]; - firstPassPixels[x, y] = window.ComputeWeightedRowSum(tempRowBuffer); - } - } - } - } - - // Now process the rows. - for (int y = minY; y < maxY; y++) - { - // Ensure offsets are normalised for cropping and padding. - WeightsWindow window = this.VerticalWeights.Weights[y - startY]; - - if (this.Compand) - { - for (int x = 0; x < width; x++) - { - // 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); + // 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; + } } - } - } + }); } source.SwapPixelsBuffers(targetPixels); From dce492971e0fc410247a5fa51af931226780141d Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sat, 18 Mar 2017 20:40:38 +0100 Subject: [PATCH 10/12] moved ResizeProfilingBenchmarks --- .../Filters/ResizeProfilingBenchmarks.cs | 30 +++++++++++++++++++ .../Processors/Filters/ResizeTests.cs | 26 ---------------- 2 files changed, 30 insertions(+), 26 deletions(-) create mode 100644 tests/ImageSharp.Tests/Processors/Filters/ResizeProfilingBenchmarks.cs diff --git a/tests/ImageSharp.Tests/Processors/Filters/ResizeProfilingBenchmarks.cs b/tests/ImageSharp.Tests/Processors/Filters/ResizeProfilingBenchmarks.cs new file mode 100644 index 000000000..06cd228a7 --- /dev/null +++ b/tests/ImageSharp.Tests/Processors/Filters/ResizeProfilingBenchmarks.cs @@ -0,0 +1,30 @@ +namespace ImageSharp.Tests +{ + 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); + } + }); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processors/Filters/ResizeTests.cs b/tests/ImageSharp.Tests/Processors/Filters/ResizeTests.cs index a95b196b7..643033f4c 100644 --- a/tests/ImageSharp.Tests/Processors/Filters/ResizeTests.cs +++ b/tests/ImageSharp.Tests/Processors/Filters/ResizeTests.cs @@ -8,32 +8,6 @@ namespace ImageSharp.Tests using System.IO; using Processing; using Xunit; - using Xunit.Abstractions; - - public class ResizeProfilingBenchmarks : MeasureFixture - { - public ResizeProfilingBenchmarks(ITestOutputHelper output) - : base(output) - { - } - - public int ExecutionCount { get; set; } = 50; - - [Theory] - [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); - } - }); - } - } public class ResizeTests : FileTestBase { From 8f570854f04bc36bdabcf74aa49c07be483ccf0d Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sat, 18 Mar 2017 21:13:58 +0100 Subject: [PATCH 11/12] fixed PinnedImageBufferTests.GetRowSpanY() --- tests/ImageSharp.Tests/Common/PinnedImageBufferTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/ImageSharp.Tests/Common/PinnedImageBufferTests.cs b/tests/ImageSharp.Tests/Common/PinnedImageBufferTests.cs index 7756b0aa9..a23f93a70 100644 --- a/tests/ImageSharp.Tests/Common/PinnedImageBufferTests.cs +++ b/tests/ImageSharp.Tests/Common/PinnedImageBufferTests.cs @@ -65,7 +65,7 @@ namespace ImageSharp.Tests.Common Assert.Equal(width * y, span.Start); Assert.Equal(width, span.Length); - Assert.Equal(buffer.Pointer + sizeof(int) * width * y, span.PointerAtOffset); + Assert.Equal(buffer.Pointer + sizeof(Foo) * width * y, span.PointerAtOffset); } } @@ -81,7 +81,7 @@ namespace ImageSharp.Tests.Common Assert.Equal(width * y + x, span.Start); Assert.Equal(width - x, span.Length); - Assert.Equal(buffer.Pointer + sizeof(int) * (width * y + x), span.PointerAtOffset); + Assert.Equal(buffer.Pointer + sizeof(Foo) * (width * y + x), span.PointerAtOffset); } } From cd1e7b3b97d55de4647ed789be9015f30a497e81 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Tue, 21 Mar 2017 03:40:32 +0100 Subject: [PATCH 12/12] weight printing experiments, minor changes, comments --- .../ResamplingWeightedProcessor.Weights.cs | 9 ++-- .../Transforms/ResamplingWeightedProcessor.cs | 53 ++++++++++--------- .../Filters/ResizeProfilingBenchmarks.cs | 31 +++++++++++ 3 files changed, 63 insertions(+), 30 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.Weights.cs b/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.Weights.cs index 785a1f20c..24d898fee 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.Weights.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.Weights.cs @@ -12,7 +12,7 @@ namespace ImageSharp.Processing.Processors /// /// Points to a collection of of weights allocated in . /// - protected unsafe struct WeightsWindow + internal unsafe struct WeightsWindow { /// /// The local left index position @@ -22,6 +22,8 @@ namespace ImageSharp.Processing.Processors /// /// 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; /// @@ -129,7 +131,7 @@ namespace ImageSharp.Processing.Processors /// /// Holds the values in an optimized contigous memory region. /// - protected class WeightsBuffer : IDisposable + internal class WeightsBuffer : IDisposable { private PinnedImageBuffer dataBuffer; @@ -140,8 +142,7 @@ namespace ImageSharp.Processing.Processors /// The size of the destination window public WeightsBuffer(int sourceSize, int destinationSize) { - this.dataBuffer = new PinnedImageBuffer(sourceSize, destinationSize); - this.dataBuffer.Clear(); + this.dataBuffer = PinnedImageBuffer.CreateClean(sourceSize, destinationSize); this.Weights = new WeightsWindow[destinationSize]; } diff --git a/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.cs index d1a709384..255124a75 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.cs @@ -68,38 +68,14 @@ namespace ImageSharp.Processing.Processors /// protected WeightsBuffer 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 override void AfterApply(ImageBase source, Rectangle sourceRectangle) - { - base.AfterApply(source, sourceRectangle); - this.HorizontalWeights?.Dispose(); - this.HorizontalWeights = null; - this.VerticalWeights?.Dispose(); - this.VerticalWeights = null; - } - /// /// Computes the weights to apply at each pixel when resizing. /// /// The destination size /// The source size /// The - protected unsafe WeightsBuffer PrecomputeWeights(int destinationSize, int sourceSize) + // 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; @@ -156,5 +132,30 @@ namespace ImageSharp.Processing.Processors return result; } + + /// + 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 override void AfterApply(ImageBase source, Rectangle sourceRectangle) + { + 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/tests/ImageSharp.Tests/Processors/Filters/ResizeProfilingBenchmarks.cs b/tests/ImageSharp.Tests/Processors/Filters/ResizeProfilingBenchmarks.cs index 06cd228a7..da09aa85e 100644 --- a/tests/ImageSharp.Tests/Processors/Filters/ResizeProfilingBenchmarks.cs +++ b/tests/ImageSharp.Tests/Processors/Filters/ResizeProfilingBenchmarks.cs @@ -1,5 +1,11 @@ namespace ImageSharp.Tests { + using System.IO; + using System.Text; + + using ImageSharp.Processing; + using ImageSharp.Processing.Processors; + using Xunit; using Xunit.Abstractions; @@ -26,5 +32,30 @@ namespace ImageSharp.Tests } }); } + + // [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