mirror of https://github.com/SixLabors/ImageSharp
committed by
GitHub
28 changed files with 1329 additions and 715 deletions
@ -1,157 +0,0 @@ |
|||
// <copyright file="BufferPointer{T}.cs" company="James Jackson-South">
|
|||
// Copyright (c) James Jackson-South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
|
|||
namespace ImageSharp |
|||
{ |
|||
using System; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
|
|||
/// <summary>
|
|||
/// 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 (<see cref="Array"/>) so we can pass it to API-s like <see cref="Marshal.Copy(byte[], int, IntPtr, int)"/>
|
|||
/// - 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 <see cref="BufferPointer{T}"/> an unsafe type!
|
|||
/// - Currently the arrays provided to BufferPointer need to be pinned. This behaviour could be changed using C#7 features.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The type of elements of the array</typeparam>
|
|||
internal unsafe struct BufferPointer<T> |
|||
where T : struct |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="BufferPointer{T}"/> struct from a pinned array and an offset.
|
|||
/// </summary>
|
|||
/// <param name="array">The pinned array</param>
|
|||
/// <param name="pointerToArray">Pointer to the beginning of array</param>
|
|||
/// <param name="offset">The offset inside the array</param>
|
|||
[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<T>() * offset); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="BufferPointer{T}"/> struct from a pinned array.
|
|||
/// </summary>
|
|||
/// <param name="array">The pinned array</param>
|
|||
/// <param name="pointerToArray">Pointer to the start of 'array'</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public BufferPointer(T[] array, void* pointerToArray) |
|||
{ |
|||
DebugGuard.NotNull(array, nameof(array)); |
|||
|
|||
this.Array = array; |
|||
this.Offset = 0; |
|||
this.PointerAtOffset = (IntPtr)pointerToArray; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the array
|
|||
/// </summary>
|
|||
public T[] Array { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the offset inside <see cref="Array"/>
|
|||
/// </summary>
|
|||
public int Offset { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the offset inside <see cref="Array"/> in bytes.
|
|||
/// </summary>
|
|||
public int ByteOffset => this.Offset * Unsafe.SizeOf<T>(); |
|||
|
|||
/// <summary>
|
|||
/// Gets the pointer to the offseted array position
|
|||
/// </summary>
|
|||
public IntPtr PointerAtOffset { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the element at the specified position.
|
|||
/// </summary>
|
|||
/// <param name="index">The index from the start of this Pointer to the required element.</param>
|
|||
/// <returns>The <see typeparam="T"/> at the specified position.</returns>
|
|||
public T this[int index] |
|||
{ |
|||
get |
|||
{ |
|||
byte* ptr = ((byte*)this.PointerAtOffset) + BufferPointer.SizeOf<T>(index); |
|||
return Unsafe.Read<T>(ptr); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Convertes <see cref="BufferPointer{T}"/> instance to a raw 'void*' pointer
|
|||
/// </summary>
|
|||
/// <param name="bufferPointer">The <see cref="BufferPointer{T}"/> to convert</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static explicit operator void*(BufferPointer<T> bufferPointer) |
|||
{ |
|||
return (void*)bufferPointer.PointerAtOffset; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts <see cref="BufferPointer{T}"/> instance to a raw 'byte*' pointer
|
|||
/// </summary>
|
|||
/// <param name="bufferPointer">The <see cref="BufferPointer{T}"/> to convert</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static explicit operator byte*(BufferPointer<T> bufferPointer) |
|||
{ |
|||
return (byte*)bufferPointer.PointerAtOffset; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts <see cref="BufferPointer{T}"/> instance to <see cref="BufferPointer{Byte}"/>
|
|||
/// setting it's <see cref="Offset"/> and <see cref="PointerAtOffset"/> to correct values.
|
|||
/// </summary>
|
|||
/// <param name="source">The <see cref="BufferPointer{T}"/> to convert</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static explicit operator BufferPointer<byte>(BufferPointer<T> source) |
|||
{ |
|||
BufferPointer<byte> result = default(BufferPointer<byte>); |
|||
result.Array = Unsafe.As<byte[]>(source.Array); |
|||
result.Offset = source.Offset * Unsafe.SizeOf<T>(); |
|||
result.PointerAtOffset = source.PointerAtOffset; |
|||
return result; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Forms a slice out of the given BufferPointer, beginning at 'offset'.
|
|||
/// </summary>
|
|||
/// <param name="offset">The offset in number of elements</param>
|
|||
/// <returns>The offseted (sliced) BufferPointer</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public BufferPointer<T> Slice(int offset) |
|||
{ |
|||
BufferPointer<T> result = default(BufferPointer<T>); |
|||
result.Array = this.Array; |
|||
result.Offset = this.Offset + offset; |
|||
result.PointerAtOffset = this.PointerAtOffset + (Unsafe.SizeOf<T>() * offset); |
|||
return result; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Clears `count` elements beginning from the pointed position.
|
|||
/// </summary>
|
|||
/// <param name="count">The number of elements to clear</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public void Clear(int count) |
|||
{ |
|||
if (count < 256) |
|||
{ |
|||
Unsafe.InitBlock((void*)this.PointerAtOffset, 0, BufferPointer.USizeOf<T>(count)); |
|||
} |
|||
else |
|||
{ |
|||
System.Array.Clear(this.Array, this.Offset, count); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,237 @@ |
|||
// <copyright file="BufferSpan{T}.cs" company="James Jackson-South">
|
|||
// Copyright (c) James Jackson-South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
|
|||
namespace ImageSharp |
|||
{ |
|||
using System; |
|||
using System.Diagnostics; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
|
|||
/// <summary>
|
|||
/// Represents a contiguous region of a pinned managed array.
|
|||
/// The array is usually owned by a <see cref="PinnedBuffer{T}"/> instance.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// <see cref="BufferSpan{T}"/> 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 (<see cref="Array"/>) so we can pass it to API-s like <see cref="Marshal.Copy(byte[], int, IntPtr, int)"/>
|
|||
/// - 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 <see cref="BufferSpan{T}"/> an unsafe type!
|
|||
/// </remarks>
|
|||
/// <typeparam name="T">The type of elements of the array</typeparam>
|
|||
internal unsafe struct BufferSpan<T> |
|||
where T : struct |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="BufferSpan{T}"/> struct from a pinned array and an start.
|
|||
/// </summary>
|
|||
/// <param name="array">The pinned array</param>
|
|||
/// <param name="pointerToArray">Pointer to the beginning of the array</param>
|
|||
/// <param name="start">The index at which to begin the span.</param>
|
|||
/// <param name="length">The length</param>
|
|||
[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<T>() * start); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="BufferSpan{T}"/> struct from a pinned array and an start.
|
|||
/// </summary>
|
|||
/// <param name="array">The pinned array</param>
|
|||
/// <param name="pointerToArray">Pointer to the beginning of the array</param>
|
|||
/// <param name="start">The index at which to begin the span.</param>
|
|||
[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<T>() * start); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="BufferSpan{T}"/> struct from a pinned array.
|
|||
/// </summary>
|
|||
/// <param name="array">The pinned array</param>
|
|||
/// <param name="pointerToArray">Pointer to the start of 'array'</param>
|
|||
[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; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the backing array
|
|||
/// </summary>
|
|||
public T[] Array { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the length of the <see cref="BufferSpan{T}"/>
|
|||
/// </summary>
|
|||
public int Length { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the start inside <see cref="Array"/>
|
|||
/// </summary>
|
|||
public int Start { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the start inside <see cref="Array"/> in bytes.
|
|||
/// </summary>
|
|||
public int ByteOffset => this.Start * Unsafe.SizeOf<T>(); |
|||
|
|||
/// <summary>
|
|||
/// Gets the pointer to the offseted array position
|
|||
/// </summary>
|
|||
public IntPtr PointerAtOffset { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Returns a reference to specified element of the span.
|
|||
/// </summary>
|
|||
/// <param name="index">The index</param>
|
|||
/// <returns>The reference to the specified element</returns>
|
|||
public ref T this[int index] |
|||
{ |
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
get |
|||
{ |
|||
DebugGuard.MustBeLessThan(index, this.Length, nameof(index)); |
|||
|
|||
byte* ptr = (byte*)this.PointerAtOffset + BufferSpan.SizeOf<T>(index); |
|||
return ref Unsafe.AsRef<T>(ptr); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Convertes <see cref="BufferSpan{T}"/> instance to a raw 'void*' pointer
|
|||
/// </summary>
|
|||
/// <param name="bufferSpan">The <see cref="BufferSpan{T}"/> to convert</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static explicit operator void*(BufferSpan<T> bufferSpan) |
|||
{ |
|||
return (void*)bufferSpan.PointerAtOffset; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts <see cref="BufferSpan{T}"/> instance to a raw 'byte*' pointer
|
|||
/// </summary>
|
|||
/// <param name="bufferSpan">The <see cref="BufferSpan{T}"/> to convert</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static explicit operator byte*(BufferSpan<T> bufferSpan) |
|||
{ |
|||
return (byte*)bufferSpan.PointerAtOffset; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Converts generic <see cref="BufferSpan{T}"/> to a <see cref="BufferSpan{T}"/> of bytes
|
|||
/// setting it's <see cref="Start"/> and <see cref="PointerAtOffset"/> to correct values.
|
|||
/// </summary>
|
|||
/// <param name="source">The <see cref="BufferSpan{T}"/> to convert</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static explicit operator BufferSpan<byte>(BufferSpan<T> source) |
|||
{ |
|||
BufferSpan<byte> result = default(BufferSpan<byte>); |
|||
result.Array = Unsafe.As<byte[]>(source.Array); |
|||
result.Start = source.Start * Unsafe.SizeOf<T>(); |
|||
result.PointerAtOffset = source.PointerAtOffset; |
|||
return result; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Forms a slice out of the given BufferSpan, beginning at 'start'.
|
|||
/// </summary>
|
|||
/// <param name="start">TThe index at which to begin this slice.</param>
|
|||
/// <returns>The offseted (sliced) BufferSpan</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public BufferSpan<T> Slice(int start) |
|||
{ |
|||
DebugGuard.MustBeLessThan(start, this.Length, nameof(start)); |
|||
|
|||
BufferSpan<T> result = default(BufferSpan<T>); |
|||
result.Array = this.Array; |
|||
result.Start = this.Start + start; |
|||
result.PointerAtOffset = this.PointerAtOffset + (Unsafe.SizeOf<T>() * start); |
|||
result.Length = this.Length - start; |
|||
return result; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Forms a slice out of the given BufferSpan, beginning at 'start'.
|
|||
/// </summary>
|
|||
/// <param name="start">The index at which to begin this slice.</param>
|
|||
/// <param name="length">The desired length for the slice (exclusive).</param>
|
|||
/// <returns>The sliced BufferSpan</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public BufferSpan<T> Slice(int start, int length) |
|||
{ |
|||
DebugGuard.MustBeLessThanOrEqualTo(start, this.Length, nameof(start)); |
|||
DebugGuard.MustBeLessThanOrEqualTo(length, this.Length - start, nameof(length)); |
|||
|
|||
BufferSpan<T> result = default(BufferSpan<T>); |
|||
result.Array = this.Array; |
|||
result.Start = this.Start + start; |
|||
result.PointerAtOffset = this.PointerAtOffset + (Unsafe.SizeOf<T>() * start); |
|||
result.Length = length; |
|||
return result; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Clears `count` elements from the beginning of the span.
|
|||
/// </summary>
|
|||
/// <param name="count">The number of elements to clear</param>
|
|||
[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<T>(count)); |
|||
} |
|||
else |
|||
{ |
|||
System.Array.Clear(this.Array, this.Start, count); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Clears the the span
|
|||
/// </summary>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public void Clear() |
|||
{ |
|||
this.Clear(this.Length); |
|||
} |
|||
|
|||
[Conditional("DEBUG")] |
|||
private static void GuardArrayAndPointer(T[] array, void* pointerToArray) |
|||
{ |
|||
DebugGuard.NotNull(array, nameof(array)); |
|||
DebugGuard.IsFalse( |
|||
pointerToArray == (void*)0, |
|||
nameof(pointerToArray), |
|||
"pointerToArray should not be null pointer!"); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,31 @@ |
|||
// <copyright file="IPinnedImageBuffer{T}.cs" company="James Jackson-South">
|
|||
// Copyright (c) James Jackson-South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
|
|||
namespace ImageSharp |
|||
{ |
|||
/// <summary>
|
|||
/// An interface that represents a pinned buffer of value type objects
|
|||
/// interpreted as a 2D region of <see cref="Width"/> x <see cref="Height"/> elements.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The value type.</typeparam>
|
|||
internal interface IPinnedImageBuffer<T> |
|||
where T : struct |
|||
{ |
|||
/// <summary>
|
|||
/// Gets the width.
|
|||
/// </summary>
|
|||
int Width { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the height.
|
|||
/// </summary>
|
|||
int Height { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a <see cref="BufferSpan{T}"/> to the backing buffer.
|
|||
/// </summary>
|
|||
BufferSpan<T> Span { get; } |
|||
} |
|||
} |
|||
@ -0,0 +1,45 @@ |
|||
// <copyright file="PinnedImageBufferExtensions{T}.cs" company="James Jackson-South">
|
|||
// Copyright (c) James Jackson-South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
|
|||
namespace ImageSharp |
|||
{ |
|||
using System; |
|||
using System.Runtime.CompilerServices; |
|||
|
|||
/// <summary>
|
|||
/// Defines extension methods for <see cref="IPinnedImageBuffer{T}"/>.
|
|||
/// </summary>
|
|||
internal static class PinnedImageBufferExtensions |
|||
{ |
|||
/// <summary>
|
|||
/// Gets a <see cref="BufferSpan{T}"/> to the row 'y' beginning from the pixel at 'x'.
|
|||
/// </summary>
|
|||
/// <param name="buffer">The buffer</param>
|
|||
/// <param name="x">The x coordinate (position in the row)</param>
|
|||
/// <param name="y">The y (row) coordinate</param>
|
|||
/// <typeparam name="T">The element type</typeparam>
|
|||
/// <returns>The <see cref="BufferSpan{T}"/></returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static BufferSpan<T> GetRowSpan<T>(this IPinnedImageBuffer<T> buffer, int x, int y) |
|||
where T : struct |
|||
{ |
|||
return buffer.Span.Slice((y * buffer.Width) + x, buffer.Width - x); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets a <see cref="BufferSpan{T}"/> to the row 'y' beginning from the pixel at 'x'.
|
|||
/// </summary>
|
|||
/// <param name="buffer">The buffer</param>
|
|||
/// <param name="y">The y (row) coordinate</param>
|
|||
/// <typeparam name="T">The element type</typeparam>
|
|||
/// <returns>The <see cref="BufferSpan{T}"/></returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static BufferSpan<T> GetRowSpan<T>(this IPinnedImageBuffer<T> buffer, int y) |
|||
where T : struct |
|||
{ |
|||
return buffer.Span.Slice(y * buffer.Width, buffer.Width); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,78 @@ |
|||
// <copyright file="PinnedImageBuffer{T}.cs" company="James Jackson-South">
|
|||
// Copyright (c) James Jackson-South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
|
|||
namespace ImageSharp |
|||
{ |
|||
using System; |
|||
using System.Runtime.CompilerServices; |
|||
|
|||
/// <summary>
|
|||
/// Represents a pinned buffer of value type objects
|
|||
/// interpreted as a 2D region of <see cref="Width"/> x <see cref="Height"/> elements.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The value type.</typeparam>
|
|||
internal class PinnedImageBuffer<T> : PinnedBuffer<T>, IPinnedImageBuffer<T> |
|||
where T : struct |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="PinnedImageBuffer{T}"/> class.
|
|||
/// </summary>
|
|||
/// <param name="width">The number of elements in a row</param>
|
|||
/// <param name="height">The number of rows</param>
|
|||
public PinnedImageBuffer(int width, int height) |
|||
: base(width * height) |
|||
{ |
|||
this.Width = width; |
|||
this.Height = height; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="PinnedImageBuffer{T}"/> class.
|
|||
/// </summary>
|
|||
/// <param name="array">The array to pin</param>
|
|||
/// <param name="width">The number of elements in a row</param>
|
|||
/// <param name="height">The number of rows</param>
|
|||
public PinnedImageBuffer(T[] array, int width, int height) |
|||
: base(array, width * height) |
|||
{ |
|||
this.Width = width; |
|||
this.Height = height; |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
public int Width { get; } |
|||
|
|||
/// <inheritdoc />
|
|||
public int Height { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a reference to the element at the specified position.
|
|||
/// </summary>
|
|||
/// <param name="x">The x coordinate (row)</param>
|
|||
/// <param name="y">The y coordinate (position at row)</param>
|
|||
/// <returns>A reference to the element.</returns>
|
|||
public ref T this[int x, int y] |
|||
{ |
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
get |
|||
{ |
|||
return ref this.Array[(this.Width * y) + x]; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Creates a clean instance of <see cref="PinnedImageBuffer{T}"/> initializing it's elements with 'default(T)'.
|
|||
/// </summary>
|
|||
/// <param name="width">The number of elements in a row</param>
|
|||
/// <param name="height">The number of rows</param>
|
|||
/// <returns>The <see cref="PinnedBuffer{T}"/> instance</returns>
|
|||
public static PinnedImageBuffer<T> CreateClean(int width, int height) |
|||
{ |
|||
PinnedImageBuffer<T> buffer = new PinnedImageBuffer<T>(width, height); |
|||
buffer.Clear(); |
|||
return buffer; |
|||
} |
|||
} |
|||
} |
|||
@ -1,171 +0,0 @@ |
|||
// <copyright file="CompandingResizeProcessor.cs" company="James Jackson-South">
|
|||
// Copyright (c) James Jackson-South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
|
|||
namespace ImageSharp.Processing.Processors |
|||
{ |
|||
using System; |
|||
using System.Numerics; |
|||
using System.Threading.Tasks; |
|||
|
|||
/// <summary>
|
|||
/// 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.
|
|||
/// </summary>
|
|||
/// <typeparam name="TColor">The pixel format.</typeparam>
|
|||
internal class CompandingResizeProcessor<TColor> : ResamplingWeightedProcessor<TColor> |
|||
where TColor : struct, IPixel<TColor> |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="CompandingResizeProcessor{TColor}"/> class.
|
|||
/// </summary>
|
|||
/// <param name="sampler">The sampler to perform the resize operation.</param>
|
|||
/// <param name="width">The target width.</param>
|
|||
/// <param name="height">The target height.</param>
|
|||
public CompandingResizeProcessor(IResampler sampler, int width, int height) |
|||
: base(sampler, width, height, new Rectangle(0, 0, width, height)) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="CompandingResizeProcessor{TColor}"/> class.
|
|||
/// </summary>
|
|||
/// <param name="sampler">The sampler to perform the resize operation.</param>
|
|||
/// <param name="width">The target width.</param>
|
|||
/// <param name="height">The target height.</param>
|
|||
/// <param name="resizeRectangle">
|
|||
/// The <see cref="Rectangle"/> structure that specifies the portion of the target image object to draw to.
|
|||
/// </param>
|
|||
public CompandingResizeProcessor(IResampler sampler, int width, int height, Rectangle resizeRectangle) |
|||
: base(sampler, width, height, resizeRectangle) |
|||
{ |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override bool Compand { get; set; } = true; |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override void OnApply(ImageBase<TColor> 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<TColor> targetPixels = new PixelAccessor<TColor>(width, height)) |
|||
{ |
|||
using (PixelAccessor<TColor> 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<TColor> targetPixels = new PixelAccessor<TColor>(width, height)) |
|||
{ |
|||
using (PixelAccessor<TColor> sourcePixels = source.Lock()) |
|||
using (PixelAccessor<TColor> firstPassPixels = new PixelAccessor<TColor>(width, source.Height)) |
|||
{ |
|||
Parallel.For( |
|||
0, |
|||
sourceRectangle.Bottom, |
|||
this.ParallelOptions, |
|||
y => |
|||
{ |
|||
for (int x = minX; x < maxX; x++) |
|||
{ |
|||
// Ensure offsets are normalised for cropping and padding.
|
|||
Weight[] horizontalValues = this.HorizontalWeights[x - startX].Values; |
|||
|
|||
// Destination color components
|
|||
Vector4 destination = Vector4.Zero; |
|||
|
|||
for (int i = 0; i < horizontalValues.Length; i++) |
|||
{ |
|||
Weight xw = horizontalValues[i]; |
|||
destination += sourcePixels[xw.Index + sourceX, y].ToVector4().Expand() * xw.Value; |
|||
} |
|||
|
|||
TColor d = default(TColor); |
|||
d.PackFromVector4(destination.Compress()); |
|||
firstPassPixels[x, y] = d; |
|||
} |
|||
}); |
|||
|
|||
// Now process the rows.
|
|||
Parallel.For( |
|||
minY, |
|||
maxY, |
|||
this.ParallelOptions, |
|||
y => |
|||
{ |
|||
// Ensure offsets are normalised for cropping and padding.
|
|||
Weight[] verticalValues = this.VerticalWeights[y - startY].Values; |
|||
|
|||
for (int x = 0; x < width; x++) |
|||
{ |
|||
// Destination color components
|
|||
Vector4 destination = Vector4.Zero; |
|||
|
|||
for (int i = 0; i < verticalValues.Length; i++) |
|||
{ |
|||
Weight yw = verticalValues[i]; |
|||
destination += firstPassPixels[x, yw.Index + sourceY].ToVector4().Expand() * yw.Value; |
|||
} |
|||
|
|||
TColor d = default(TColor); |
|||
d.PackFromVector4(destination.Compress()); |
|||
targetPixels[x, y] = d; |
|||
} |
|||
}); |
|||
} |
|||
|
|||
source.SwapPixelsBuffers(targetPixels); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,176 @@ |
|||
namespace ImageSharp.Processing.Processors |
|||
{ |
|||
using System; |
|||
using System.Numerics; |
|||
using System.Runtime.CompilerServices; |
|||
|
|||
/// <content>
|
|||
/// Conains the definition of <see cref="WeightsWindow"/> and <see cref="WeightsBuffer"/>.
|
|||
/// </content>
|
|||
internal abstract partial class ResamplingWeightedProcessor<TColor> |
|||
{ |
|||
/// <summary>
|
|||
/// Points to a collection of of weights allocated in <see cref="WeightsBuffer"/>.
|
|||
/// </summary>
|
|||
internal unsafe struct WeightsWindow |
|||
{ |
|||
/// <summary>
|
|||
/// The local left index position
|
|||
/// </summary>
|
|||
public int Left; |
|||
|
|||
/// <summary>
|
|||
/// The span of weights pointing to <see cref="WeightsBuffer"/>.
|
|||
/// </summary>
|
|||
// TODO: In the case of switching to official System.Memory and System.Buffers.Primitives this should be System.Buffers.Buffer<T> (formerly Memory<T>), because Span<T> is stack-only!
|
|||
// see: https://github.com/dotnet/corefxlab/blob/873d35ebed7264e2f9adb556f3b61bebc12395d6/docs/specs/memory.md
|
|||
public BufferSpan<float> Span; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="WeightsWindow"/> struct.
|
|||
/// </summary>
|
|||
/// <param name="left">The local left index</param>
|
|||
/// <param name="span">The span</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
internal WeightsWindow(int left, BufferSpan<float> span) |
|||
{ |
|||
this.Left = left; |
|||
this.Span = span; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets an unsafe float* pointer to the beginning of <see cref="Span"/>.
|
|||
/// </summary>
|
|||
public float* Ptr => (float*)this.Span.PointerAtOffset; |
|||
|
|||
/// <summary>
|
|||
/// Gets the lenghth of the weights window
|
|||
/// </summary>
|
|||
public int Length => this.Span.Length; |
|||
|
|||
/// <summary>
|
|||
/// Computes the sum of vectors in 'rowSpan' weighted by weight values, pointed by this <see cref="WeightsWindow"/> instance.
|
|||
/// </summary>
|
|||
/// <param name="rowSpan">The input span of vectors</param>
|
|||
/// <returns>The weighted sum</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public Vector4 ComputeWeightedRowSum(BufferSpan<Vector4> rowSpan) |
|||
{ |
|||
float* horizontalValues = this.Ptr; |
|||
int left = this.Left; |
|||
Vector4* vecPtr = (Vector4*)rowSpan.PointerAtOffset; |
|||
vecPtr += left; |
|||
|
|||
// Destination color components
|
|||
Vector4 result = Vector4.Zero; |
|||
|
|||
for (int i = 0; i < this.Length; i++) |
|||
{ |
|||
float weight = horizontalValues[i]; |
|||
result += (*vecPtr) * weight; |
|||
vecPtr++; |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Computes the sum of vectors in 'rowSpan' weighted by weight values, pointed by this <see cref="WeightsWindow"/> instance.
|
|||
/// Applies <see cref="Vector4Extensions.Expand(float)"/> to all input vectors.
|
|||
/// </summary>
|
|||
/// <param name="rowSpan">The input span of vectors</param>
|
|||
/// <returns>The weighted sum</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public Vector4 ComputeExpandedWeightedRowSum(BufferSpan<Vector4> rowSpan) |
|||
{ |
|||
float* horizontalValues = this.Ptr; |
|||
int left = this.Left; |
|||
Vector4* vecPtr = (Vector4*)rowSpan.PointerAtOffset; |
|||
vecPtr += left; |
|||
|
|||
// Destination color components
|
|||
Vector4 result = Vector4.Zero; |
|||
|
|||
for (int i = 0; i < this.Length; i++) |
|||
{ |
|||
float weight = horizontalValues[i]; |
|||
result += (*vecPtr).Expand() * weight; |
|||
vecPtr++; |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Computes the sum of vectors in 'firstPassPixels' at a column pointed by 'x',
|
|||
/// weighted by weight values, pointed by this <see cref="WeightsWindow"/> instance.
|
|||
/// </summary>
|
|||
/// <param name="firstPassPixels">The buffer of input vectors in row first order</param>
|
|||
/// <param name="x">The column position</param>
|
|||
/// <returns>The weighted sum</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public Vector4 ComputeWeightedColumnSum(PinnedImageBuffer<Vector4> 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; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Holds the <see cref="WeightsWindow"/> values in an optimized contigous memory region.
|
|||
/// </summary>
|
|||
internal class WeightsBuffer : IDisposable |
|||
{ |
|||
private PinnedImageBuffer<float> dataBuffer; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="WeightsBuffer"/> class.
|
|||
/// </summary>
|
|||
/// <param name="sourceSize">The size of the source window</param>
|
|||
/// <param name="destinationSize">The size of the destination window</param>
|
|||
public WeightsBuffer(int sourceSize, int destinationSize) |
|||
{ |
|||
this.dataBuffer = PinnedImageBuffer<float>.CreateClean(sourceSize, destinationSize); |
|||
this.Weights = new WeightsWindow[destinationSize]; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the calculated <see cref="Weights"/> values.
|
|||
/// </summary>
|
|||
public WeightsWindow[] Weights { get; } |
|||
|
|||
/// <summary>
|
|||
/// Disposes <see cref="WeightsBuffer"/> instance releasing it's backing buffer.
|
|||
/// </summary>
|
|||
public void Dispose() |
|||
{ |
|||
this.dataBuffer.Dispose(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Slices a weights value at the given positions.
|
|||
/// </summary>
|
|||
/// <param name="destIdx">The index in destination buffer</param>
|
|||
/// <param name="leftIdx">The local left index value</param>
|
|||
/// <param name="rightIdx">The local right index value</param>
|
|||
/// <returns>The weights</returns>
|
|||
public WeightsWindow GetWeightsWindow(int destIdx, int leftIdx, int rightIdx) |
|||
{ |
|||
BufferSpan<float> span = this.dataBuffer.GetRowSpan(destIdx).Slice(leftIdx, rightIdx - leftIdx); |
|||
return new WeightsWindow(leftIdx, span); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,106 @@ |
|||
// ReSharper disable InconsistentNaming
|
|||
namespace ImageSharp.Tests.Common |
|||
{ |
|||
using System.Runtime.CompilerServices; |
|||
|
|||
using Xunit; |
|||
|
|||
using static TestStructs; |
|||
|
|||
public unsafe class PinnedImageBufferTests |
|||
{ |
|||
[Theory] |
|||
[InlineData(7, 42)] |
|||
[InlineData(1025, 17)] |
|||
public void Construct(int width, int height) |
|||
{ |
|||
using (PinnedImageBuffer<Foo> buffer = new PinnedImageBuffer<Foo>(width, height)) |
|||
{ |
|||
Assert.Equal(width, buffer.Width); |
|||
Assert.Equal(height, buffer.Height); |
|||
Assert.Equal(width * height, buffer.Length); |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(7, 42)] |
|||
[InlineData(1025, 17)] |
|||
public void Construct_FromExternalArray(int width, int height) |
|||
{ |
|||
Foo[] array = new Foo[width * height + 10]; |
|||
using (PinnedImageBuffer<Foo> buffer = new PinnedImageBuffer<Foo>(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<int> buffer = PinnedImageBuffer<int>.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<Foo> buffer = new PinnedImageBuffer<Foo>(width, height)) |
|||
{ |
|||
BufferSpan<Foo> span = buffer.GetRowSpan(y); |
|||
|
|||
Assert.Equal(width * y, span.Start); |
|||
Assert.Equal(width, span.Length); |
|||
Assert.Equal(buffer.Pointer + sizeof(Foo) * width * y, span.PointerAtOffset); |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(7, 42, 0, 0)] |
|||
[InlineData(7, 42, 3, 10)] |
|||
[InlineData(17, 42, 0, 41)] |
|||
public void GetRowSpanXY(int width, int height, int x, int y) |
|||
{ |
|||
using (PinnedImageBuffer<Foo> buffer = new PinnedImageBuffer<Foo>(width, height)) |
|||
{ |
|||
BufferSpan<Foo> span = buffer.GetRowSpan(x, y); |
|||
|
|||
Assert.Equal(width * y + x, span.Start); |
|||
Assert.Equal(width - x, span.Length); |
|||
Assert.Equal(buffer.Pointer + sizeof(Foo) * (width * y + x), span.PointerAtOffset); |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(42, 8, 0, 0)] |
|||
[InlineData(400, 1000, 20, 10)] |
|||
[InlineData(99, 88, 98, 87)] |
|||
public void Indexer(int width, int height, int x, int y) |
|||
{ |
|||
using (PinnedImageBuffer<Foo> buffer = new PinnedImageBuffer<Foo>(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)); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
|
|||
|
|||
/// <summary>
|
|||
/// sizeof(AlignedFoo) == sizeof(long)
|
|||
/// </summary>
|
|||
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; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,61 @@ |
|||
namespace ImageSharp.Tests |
|||
{ |
|||
using System.IO; |
|||
using System.Text; |
|||
|
|||
using ImageSharp.Processing; |
|||
using ImageSharp.Processing.Processors; |
|||
|
|||
using Xunit; |
|||
using Xunit.Abstractions; |
|||
|
|||
public class ResizeProfilingBenchmarks : MeasureFixture |
|||
{ |
|||
public ResizeProfilingBenchmarks(ITestOutputHelper output) |
|||
: base(output) |
|||
{ |
|||
} |
|||
|
|||
public int ExecutionCount { get; set; } = 50; |
|||
|
|||
// [Theory] // Benchmark, enable manually!
|
|||
[InlineData(100, 100)] |
|||
[InlineData(2000, 2000)] |
|||
public void ResizeBicubic(int width, int height) |
|||
{ |
|||
this.Measure(this.ExecutionCount, |
|||
() => |
|||
{ |
|||
using (Image image = new Image(width, height)) |
|||
{ |
|||
image.Resize(width / 4, height / 4); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
// [Fact]
|
|||
public void PrintWeightsData() |
|||
{ |
|||
ResizeProcessor<Color> proc = new ResizeProcessor<Color>(new BicubicResampler(), 200, 200); |
|||
|
|||
ResamplingWeightedProcessor<Color>.WeightsBuffer weights = proc.PrecomputeWeights(200, 500); |
|||
|
|||
StringBuilder bld = new StringBuilder(); |
|||
|
|||
foreach (ResamplingWeightedProcessor<Color>.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());
|
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue