Browse Source

Merge branch 'feature/icc' of https://github.com/JimBobSquarePants/ImageSharp into feature/icc

af/merge-core
Johannes Bildstein 9 years ago
parent
commit
c2e98fc987
  1. 2
      src/ImageSharp.Drawing/Brushes/ImageBrush{TColor}.cs
  2. 2
      src/ImageSharp.Drawing/Brushes/PatternBrush{TColor}.cs
  3. 2
      src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs
  4. 2
      src/ImageSharp.Drawing/Brushes/RecolorBrush{TColor}.cs
  5. 2
      src/ImageSharp.Drawing/Brushes/SolidBrush{TColor}.cs
  6. 32
      src/ImageSharp/Colors/Color.BulkOperations.cs
  7. 84
      src/ImageSharp/Colors/PackedPixel/BulkPixelOperations{TColor}.cs
  8. 157
      src/ImageSharp/Common/Memory/BufferPointer{T}.cs
  9. 28
      src/ImageSharp/Common/Memory/BufferSpan.cs
  10. 237
      src/ImageSharp/Common/Memory/BufferSpan{T}.cs
  11. 31
      src/ImageSharp/Common/Memory/IPinnedImageBuffer{T}.cs
  12. 79
      src/ImageSharp/Common/Memory/PinnedBuffer{T}.cs
  13. 45
      src/ImageSharp/Common/Memory/PinnedImageBufferExtensions.cs
  14. 78
      src/ImageSharp/Common/Memory/PinnedImageBuffer{T}.cs
  15. 58
      src/ImageSharp/Image/PixelAccessor{TColor}.cs
  16. 6
      src/ImageSharp/Image/PixelArea{TColor}.cs
  17. 1
      src/ImageSharp/MetaData/Profiles/ICC/Enums/IccProfileTag.cs
  18. 171
      src/ImageSharp/Processing/Processors/Transforms/CompandingResizeProcessor.cs
  19. 176
      src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.Weights.cs
  20. 93
      src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.cs
  21. 85
      src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs
  22. 12
      src/ImageSharp/Processing/Transforms/Resize.cs
  23. 9
      tests/ImageSharp.Sandbox46/Program.cs
  24. 8
      tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs
  25. 276
      tests/ImageSharp.Tests/Common/BufferSpanTests.cs
  26. 140
      tests/ImageSharp.Tests/Common/PinnedBufferTests.cs
  27. 106
      tests/ImageSharp.Tests/Common/PinnedImageBufferTests.cs
  28. 62
      tests/ImageSharp.Tests/Common/TestStructs.cs
  29. 61
      tests/ImageSharp.Tests/Processors/Filters/ResizeProfilingBenchmarks.cs

2
src/ImageSharp.Drawing/Brushes/ImageBrush{TColor}.cs

@ -119,7 +119,7 @@ namespace ImageSharp.Drawing.Brushes
using (PinnedBuffer<float> buffer = new PinnedBuffer<float>(scanlineBuffer))
{
BufferPointer<float> slice = buffer.Slice(offset);
BufferSpan<float> slice = buffer.Slice(offset);
for (int xPos = 0; xPos < scanlineWidth; xPos++)
{

2
src/ImageSharp.Drawing/Brushes/PatternBrush{TColor}.cs

@ -152,7 +152,7 @@ namespace ImageSharp.Drawing.Brushes
using (PinnedBuffer<float> buffer = new PinnedBuffer<float>(scanlineBuffer))
{
BufferPointer<float> slice = buffer.Slice(offset);
BufferSpan<float> slice = buffer.Slice(offset);
for (int xPos = 0; xPos < scanlineWidth; xPos++)
{

2
src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs

@ -57,7 +57,7 @@ namespace ImageSharp.Drawing.Processors
using (PinnedBuffer<float> buffer = new PinnedBuffer<float>(scanlineBuffer))
{
BufferPointer<float> slice = buffer.Slice(offset);
BufferSpan<float> slice = buffer.Slice(offset);
for (int xPos = 0; xPos < scanlineWidth; xPos++)
{

2
src/ImageSharp.Drawing/Brushes/RecolorBrush{TColor}.cs

@ -143,7 +143,7 @@ namespace ImageSharp.Drawing.Brushes
using (PinnedBuffer<float> buffer = new PinnedBuffer<float>(scanlineBuffer))
{
BufferPointer<float> slice = buffer.Slice(offset);
BufferSpan<float> slice = buffer.Slice(offset);
for (int xPos = 0; xPos < scanlineWidth; xPos++)
{

2
src/ImageSharp.Drawing/Brushes/SolidBrush{TColor}.cs

@ -91,7 +91,7 @@ namespace ImageSharp.Drawing.Brushes
using (PinnedBuffer<float> buffer = new PinnedBuffer<float>(scanlineBuffer))
{
BufferPointer<float> slice = buffer.Slice(offset);
BufferSpan<float> slice = buffer.Slice(offset);
for (int xPos = 0; xPos < scanlineWidth; xPos++)
{

32
src/ImageSharp/Colors/Color.BulkOperations.cs

@ -24,8 +24,8 @@ namespace ImageSharp
/// SIMD optimized bulk implementation of <see cref="IPixel.PackFromVector4(Vector4)"/>
/// that works only with `count` divisible by <see cref="Vector{UInt32}.Count"/>.
/// </summary>
/// <param name="sourceColors">The <see cref="BufferPointer{T}"/> to the source colors.</param>
/// <param name="destVectors">The <see cref="BufferPointer{T}"/> to the dstination vectors.</param>
/// <param name="sourceColors">The <see cref="BufferSpan{T}"/> to the source colors.</param>
/// <param name="destVectors">The <see cref="BufferSpan{T}"/> to the dstination vectors.</param>
/// <param name="count">The number of pixels to convert.</param>
/// <remarks>
/// Implementation adapted from:
@ -38,8 +38,8 @@ namespace ImageSharp
/// </see>
/// </remarks>
internal static unsafe void ToVector4SimdAligned(
BufferPointer<Color> sourceColors,
BufferPointer<Vector4> destVectors,
BufferSpan<Color> sourceColors,
BufferSpan<Vector4> destVectors,
int count)
{
int vecSize = Vector<uint>.Count;
@ -85,12 +85,12 @@ namespace ImageSharp
vf.CopyTo(fTemp, i);
}
BufferPointer.Copy<uint>(tempBuf, (BufferPointer<byte>)destVectors, unpackedRawCount);
BufferSpan.Copy<uint>(tempBuf, (BufferSpan<byte>)destVectors, unpackedRawCount);
}
}
/// <inheritdoc />
internal override void ToVector4(BufferPointer<Color> sourceColors, BufferPointer<Vector4> destVectors, int count)
internal override void ToVector4(BufferSpan<Color> sourceColors, BufferSpan<Vector4> destVectors, int count)
{
if (count < 256)
{
@ -117,7 +117,7 @@ namespace ImageSharp
}
/// <inheritdoc />
internal override unsafe void PackFromXyzBytes(BufferPointer<byte> sourceBytes, BufferPointer<Color> destColors, int count)
internal override unsafe void PackFromXyzBytes(BufferSpan<byte> sourceBytes, BufferSpan<Color> destColors, int count)
{
byte* source = (byte*)sourceBytes;
byte* destination = (byte*)destColors;
@ -132,7 +132,7 @@ namespace ImageSharp
}
/// <inheritdoc />
internal override unsafe void ToXyzBytes(BufferPointer<Color> sourceColors, BufferPointer<byte> destBytes, int count)
internal override unsafe void ToXyzBytes(BufferSpan<Color> sourceColors, BufferSpan<byte> destBytes, int count)
{
byte* source = (byte*)sourceColors;
byte* destination = (byte*)destBytes;
@ -149,19 +149,19 @@ namespace ImageSharp
}
/// <inheritdoc />
internal override void PackFromXyzwBytes(BufferPointer<byte> sourceBytes, BufferPointer<Color> destColors, int count)
internal override void PackFromXyzwBytes(BufferSpan<byte> sourceBytes, BufferSpan<Color> destColors, int count)
{
BufferPointer.Copy(sourceBytes, destColors, count);
BufferSpan.Copy(sourceBytes, destColors, count);
}
/// <inheritdoc />
internal override void ToXyzwBytes(BufferPointer<Color> sourceColors, BufferPointer<byte> destBytes, int count)
internal override void ToXyzwBytes(BufferSpan<Color> sourceColors, BufferSpan<byte> destBytes, int count)
{
BufferPointer.Copy(sourceColors, destBytes, count);
BufferSpan.Copy(sourceColors, destBytes, count);
}
/// <inheritdoc />
internal override unsafe void PackFromZyxBytes(BufferPointer<byte> sourceBytes, BufferPointer<Color> destColors, int count)
internal override unsafe void PackFromZyxBytes(BufferSpan<byte> sourceBytes, BufferSpan<Color> destColors, int count)
{
byte* source = (byte*)sourceBytes;
byte* destination = (byte*)destColors;
@ -176,7 +176,7 @@ namespace ImageSharp
}
/// <inheritdoc />
internal override unsafe void ToZyxBytes(BufferPointer<Color> sourceColors, BufferPointer<byte> destBytes, int count)
internal override unsafe void ToZyxBytes(BufferSpan<Color> sourceColors, BufferSpan<byte> destBytes, int count)
{
byte* source = (byte*)sourceColors;
byte* destination = (byte*)destBytes;
@ -193,7 +193,7 @@ namespace ImageSharp
}
/// <inheritdoc />
internal override unsafe void PackFromZyxwBytes(BufferPointer<byte> sourceBytes, BufferPointer<Color> destColors, int count)
internal override unsafe void PackFromZyxwBytes(BufferSpan<byte> sourceBytes, BufferSpan<Color> destColors, int count)
{
byte* source = (byte*)sourceBytes;
byte* destination = (byte*)destColors;
@ -208,7 +208,7 @@ namespace ImageSharp
}
/// <inheritdoc />
internal override unsafe void ToZyxwBytes(BufferPointer<Color> sourceColors, BufferPointer<byte> destBytes, int count)
internal override unsafe void ToZyxwBytes(BufferSpan<Color> sourceColors, BufferSpan<byte> destBytes, int count)
{
byte* source = (byte*)sourceColors;
byte* destination = (byte*)destBytes;

84
src/ImageSharp/Colors/PackedPixel/BulkPixelOperations{TColor}.cs

@ -29,12 +29,12 @@ namespace ImageSharp
/// <summary>
/// Bulk version of <see cref="IPixel.PackFromVector4(Vector4)"/>
/// </summary>
/// <param name="sourceVectors">The <see cref="BufferPointer{T}"/> to the source vectors.</param>
/// <param name="destColors">The <see cref="BufferPointer{T}"/> to the destination colors.</param>
/// <param name="sourceVectors">The <see cref="BufferSpan{T}"/> to the source vectors.</param>
/// <param name="destColors">The <see cref="BufferSpan{T}"/> to the destination colors.</param>
/// <param name="count">The number of pixels to convert.</param>
internal virtual void PackFromVector4(
BufferPointer<Vector4> sourceVectors,
BufferPointer<TColor> destColors,
BufferSpan<Vector4> sourceVectors,
BufferSpan<TColor> destColors,
int count)
{
Vector4* sp = (Vector4*)sourceVectors.PointerAtOffset;
@ -55,12 +55,12 @@ namespace ImageSharp
/// <summary>
/// Bulk version of <see cref="IPixel.ToVector4()"/>.
/// </summary>
/// <param name="sourceColors">The <see cref="BufferPointer{T}"/> to the source colors.</param>
/// <param name="destVectors">The <see cref="BufferPointer{T}"/> to the destination vectors.</param>
/// <param name="sourceColors">The <see cref="BufferSpan{T}"/> to the source colors.</param>
/// <param name="destVectors">The <see cref="BufferSpan{T}"/> to the destination vectors.</param>
/// <param name="count">The number of pixels to convert.</param>
internal virtual void ToVector4(
BufferPointer<TColor> sourceColors,
BufferPointer<Vector4> destVectors,
BufferSpan<TColor> sourceColors,
BufferSpan<Vector4> destVectors,
int count)
{
byte* sp = (byte*)sourceColors;
@ -78,12 +78,12 @@ namespace ImageSharp
/// <summary>
/// Bulk version of <see cref="IPixel.PackFromBytes(byte, byte, byte, byte)"/> that converts data in <see cref="ComponentOrder.Xyz"/>.
/// </summary>
/// <param name="sourceBytes">The <see cref="BufferPointer{T}"/> to the source bytes.</param>
/// <param name="destColors">The <see cref="BufferPointer{T}"/> to the destination colors.</param>
/// <param name="sourceBytes">The <see cref="BufferSpan{T}"/> to the source bytes.</param>
/// <param name="destColors">The <see cref="BufferSpan{T}"/> to the destination colors.</param>
/// <param name="count">The number of pixels to convert.</param>
internal virtual void PackFromXyzBytes(
BufferPointer<byte> sourceBytes,
BufferPointer<TColor> destColors,
BufferSpan<byte> sourceBytes,
BufferSpan<TColor> destColors,
int count)
{
byte* sp = (byte*)sourceBytes;
@ -102,15 +102,15 @@ namespace ImageSharp
/// <summary>
/// Bulk version of <see cref="IPixel.ToXyzBytes(byte[], int)"/>.
/// </summary>
/// <param name="sourceColors">The <see cref="BufferPointer{T}"/> to the source colors.</param>
/// <param name="destBytes">The <see cref="BufferPointer{T}"/> to the destination bytes.</param>
/// <param name="sourceColors">The <see cref="BufferSpan{T}"/> to the source colors.</param>
/// <param name="destBytes">The <see cref="BufferSpan{T}"/> to the destination bytes.</param>
/// <param name="count">The number of pixels to convert.</param>
internal virtual void ToXyzBytes(BufferPointer<TColor> sourceColors, BufferPointer<byte> destBytes, int count)
internal virtual void ToXyzBytes(BufferSpan<TColor> sourceColors, BufferSpan<byte> 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<TColor>(sp);
c.ToXyzBytes(dest, i);
@ -121,12 +121,12 @@ namespace ImageSharp
/// <summary>
/// Bulk version of <see cref="IPixel.PackFromBytes(byte, byte, byte, byte)"/> that converts data in <see cref="ComponentOrder.Xyzw"/>.
/// </summary>
/// <param name="sourceBytes">The <see cref="BufferPointer{T}"/> to the source bytes.</param>
/// <param name="destColors">The <see cref="BufferPointer{T}"/> to the destination colors.</param>
/// <param name="sourceBytes">The <see cref="BufferSpan{T}"/> to the source bytes.</param>
/// <param name="destColors">The <see cref="BufferSpan{T}"/> to the destination colors.</param>
/// <param name="count">The number of pixels to convert.</param>
internal virtual void PackFromXyzwBytes(
BufferPointer<byte> sourceBytes,
BufferPointer<TColor> destColors,
BufferSpan<byte> sourceBytes,
BufferSpan<TColor> destColors,
int count)
{
byte* sp = (byte*)sourceBytes;
@ -145,18 +145,18 @@ namespace ImageSharp
/// <summary>
/// Bulk version of <see cref="IPixel.ToXyzwBytes(byte[], int)"/>.
/// </summary>
/// <param name="sourceColors">The <see cref="BufferPointer{T}"/> to the source colors.</param>
/// <param name="destBytes">The <see cref="BufferPointer{T}"/> to the destination bytes.</param>
/// <param name="sourceColors">The <see cref="BufferSpan{T}"/> to the source colors.</param>
/// <param name="destBytes">The <see cref="BufferSpan{T}"/> to the destination bytes.</param>
/// <param name="count">The number of pixels to convert.</param>
internal virtual void ToXyzwBytes(
BufferPointer<TColor> sourceColors,
BufferPointer<byte> destBytes,
BufferSpan<TColor> sourceColors,
BufferSpan<byte> 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<TColor>(sp);
c.ToXyzwBytes(dest, i);
@ -167,12 +167,12 @@ namespace ImageSharp
/// <summary>
/// Bulk version of <see cref="IPixel.PackFromBytes(byte, byte, byte, byte)"/> that converts data in <see cref="ComponentOrder.Zyx"/>.
/// </summary>
/// <param name="sourceBytes">The <see cref="BufferPointer{T}"/> to the source bytes.</param>
/// <param name="destColors">The <see cref="BufferPointer{T}"/> to the destination colors.</param>
/// <param name="sourceBytes">The <see cref="BufferSpan{T}"/> to the source bytes.</param>
/// <param name="destColors">The <see cref="BufferSpan{T}"/> to the destination colors.</param>
/// <param name="count">The number of pixels to convert.</param>
internal virtual void PackFromZyxBytes(
BufferPointer<byte> sourceBytes,
BufferPointer<TColor> destColors,
BufferSpan<byte> sourceBytes,
BufferSpan<TColor> destColors,
int count)
{
byte* sp = (byte*)sourceBytes;
@ -191,15 +191,15 @@ namespace ImageSharp
/// <summary>
/// Bulk version of <see cref="IPixel.ToZyxBytes(byte[], int)"/>.
/// </summary>
/// <param name="sourceColors">The <see cref="BufferPointer{T}"/> to the source colors.</param>
/// <param name="destBytes">The <see cref="BufferPointer{T}"/> to the destination bytes.</param>
/// <param name="sourceColors">The <see cref="BufferSpan{T}"/> to the source colors.</param>
/// <param name="destBytes">The <see cref="BufferSpan{T}"/> to the destination bytes.</param>
/// <param name="count">The number of pixels to convert.</param>
internal virtual void ToZyxBytes(BufferPointer<TColor> sourceColors, BufferPointer<byte> destBytes, int count)
internal virtual void ToZyxBytes(BufferSpan<TColor> sourceColors, BufferSpan<byte> 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<TColor>(sp);
c.ToZyxBytes(dest, i);
@ -210,12 +210,12 @@ namespace ImageSharp
/// <summary>
/// Bulk version of <see cref="IPixel.PackFromBytes(byte, byte, byte, byte)"/> that converts data in <see cref="ComponentOrder.Zyxw"/>.
/// </summary>
/// <param name="sourceBytes">The <see cref="BufferPointer{T}"/> to the source bytes.</param>
/// <param name="destColors">The <see cref="BufferPointer{T}"/> to the destination colors.</param>
/// <param name="sourceBytes">The <see cref="BufferSpan{T}"/> to the source bytes.</param>
/// <param name="destColors">The <see cref="BufferSpan{T}"/> to the destination colors.</param>
/// <param name="count">The number of pixels to convert.</param>
internal virtual void PackFromZyxwBytes(
BufferPointer<byte> sourceBytes,
BufferPointer<TColor> destColors,
BufferSpan<byte> sourceBytes,
BufferSpan<TColor> destColors,
int count)
{
byte* sp = (byte*)sourceBytes;
@ -234,18 +234,18 @@ namespace ImageSharp
/// <summary>
/// Bulk version of <see cref="IPixel.ToZyxwBytes(byte[], int)"/>.
/// </summary>
/// <param name="sourceColors">The <see cref="BufferPointer{T}"/> to the source colors.</param>
/// <param name="destBytes">The <see cref="BufferPointer{T}"/> to the destination bytes.</param>
/// <param name="sourceColors">The <see cref="BufferSpan{T}"/> to the source colors.</param>
/// <param name="destBytes">The <see cref="BufferSpan{T}"/> to the destination bytes.</param>
/// <param name="count">The number of pixels to convert.</param>
internal virtual void ToZyxwBytes(
BufferPointer<TColor> sourceColors,
BufferPointer<byte> destBytes,
BufferSpan<TColor> sourceColors,
BufferSpan<byte> 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<TColor>(sp);
c.ToZyxwBytes(dest, i);

157
src/ImageSharp/Common/Memory/BufferPointer{T}.cs

@ -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&lt;T&gt; 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);
}
}
}
}

28
src/ImageSharp/Common/Memory/BufferPointer.cs → src/ImageSharp/Common/Memory/BufferSpan.cs

@ -1,4 +1,4 @@
// <copyright file="BufferPointer.cs" company="James Jackson-South">
// <copyright file="BufferSpan.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
@ -11,9 +11,9 @@ namespace ImageSharp
using System.Runtime.InteropServices;
/// <summary>
/// Utility methods for <see cref="BufferPointer{T}"/>
/// Utility methods for <see cref="BufferSpan{T}"/>
/// </summary>
internal static class BufferPointer
internal static class BufferSpan
{
/// <summary>
/// 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'
/// </summary>
/// <typeparam name="T">The element type.</typeparam>
/// <param name="source">The input <see cref="BufferPointer{T}"/></param>
/// <param name="destination">The destination <see cref="BufferPointer{T}"/>.</param>
/// <param name="source">The input <see cref="BufferSpan{T}"/></param>
/// <param name="destination">The destination <see cref="BufferSpan{T}"/>.</param>
/// <param name="count">The number of elements to copy</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Copy<T>(BufferPointer<T> source, BufferPointer<T> destination, int count)
public static void Copy<T>(BufferSpan<T> source, BufferSpan<T> destination, int count)
where T : struct
{
CopyImpl(source, destination, count);
@ -42,7 +42,7 @@ namespace ImageSharp
/// <param name="destination">The destination buffer.</param>
/// <param name="countInSource">The number of elements to copy from 'source'</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Copy<T>(BufferPointer<T> source, BufferPointer<byte> destination, int countInSource)
public static void Copy<T>(BufferSpan<T> source, BufferSpan<byte> destination, int countInSource)
where T : struct
{
CopyImpl(source, destination, countInSource);
@ -56,14 +56,14 @@ namespace ImageSharp
/// <param name="destination">The destination buffer"/></param>
/// <param name="countInDest">The number of <typeparamref name="T"/> elements to copy.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe void Copy<T>(BufferPointer<byte> source, BufferPointer<T> destination, int countInDest)
public static unsafe void Copy<T>(BufferSpan<byte> source, BufferSpan<T> destination, int countInDest)
where T : struct
{
int byteCount = SizeOf<T>(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<T>(count);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static unsafe void CopyImpl<T, TDest>(BufferPointer<T> source, BufferPointer<TDest> destination, int count)
private static unsafe void CopyImpl<T, TDest>(BufferSpan<T> source, BufferSpan<TDest> destination, int count)
where T : struct
where TDest : struct
{
@ -103,22 +103,22 @@ namespace ImageSharp
{
if (Unsafe.SizeOf<T>() == sizeof(long))
{
Marshal.Copy(Unsafe.As<long[]>(source.Array), source.Offset, destination.PointerAtOffset, count);
Marshal.Copy(Unsafe.As<long[]>(source.Array), source.Start, destination.PointerAtOffset, count);
return;
}
else if (Unsafe.SizeOf<T>() == sizeof(int))
{
Marshal.Copy(Unsafe.As<int[]>(source.Array), source.Offset, destination.PointerAtOffset, count);
Marshal.Copy(Unsafe.As<int[]>(source.Array), source.Start, destination.PointerAtOffset, count);
return;
}
else if (Unsafe.SizeOf<T>() == sizeof(short))
{
Marshal.Copy(Unsafe.As<short[]>(source.Array), source.Offset, destination.PointerAtOffset, count);
Marshal.Copy(Unsafe.As<short[]>(source.Array), source.Start, destination.PointerAtOffset, count);
return;
}
else if (Unsafe.SizeOf<T>() == sizeof(byte))
{
Marshal.Copy(Unsafe.As<byte[]>(source.Array), source.Offset, destination.PointerAtOffset, count);
Marshal.Copy(Unsafe.As<byte[]>(source.Array), source.Start, destination.PointerAtOffset, count);
return;
}
}

237
src/ImageSharp/Common/Memory/BufferSpan{T}.cs

@ -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&lt;T&gt;, 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!");
}
}
}

31
src/ImageSharp/Common/Memory/IPinnedImageBuffer{T}.cs

@ -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; }
}
}

79
src/ImageSharp/Common/Memory/PinnedBuffer{T}.cs

@ -11,7 +11,7 @@ namespace ImageSharp
using System.Runtime.InteropServices;
/// <summary>
/// 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.
/// </summary>
/// <typeparam name="T">The value type.</typeparam>
@ -32,11 +32,11 @@ namespace ImageSharp
/// <summary>
/// Initializes a new instance of the <see cref="PinnedBuffer{T}"/> class.
/// </summary>
/// <param name="count">The desired count of elements. (Minimum size for <see cref="Array"/>)</param>
public PinnedBuffer(int count)
/// <param name="length">The desired count of elements. (Minimum size for <see cref="Array"/>)</param>
public PinnedBuffer(int length)
{
this.Count = count;
this.Array = PixelDataPool<T>.Rent(count);
this.Length = length;
this.Array = PixelDataPool<T>.Rent(length);
this.isPoolingOwner = true;
this.Pin();
}
@ -47,7 +47,7 @@ namespace ImageSharp
/// <param name="array">The array to pin.</param>
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
/// <summary>
/// Initializes a new instance of the <see cref="PinnedBuffer{T}"/> class.
/// </summary>
/// <param name="count">The count of "relevant" elements in 'array'.</param>
/// <param name="array">The array to pin.</param>
public PinnedBuffer(int count, T[] array)
/// <param name="length">The count of "relevant" elements in 'array'.</param>
public PinnedBuffer(T[] array, int length)
{
if (array.Length < count)
if (array.Length < length)
{
throw new ArgumentException("Can't initialize a PinnedBuffer with array.Length < count", nameof(array));
}
this.Count = count;
this.Length = length;
this.Array = array;
this.isPoolingOwner = false;
this.Pin();
@ -85,9 +85,9 @@ namespace ImageSharp
public bool IsDisposedOrLostArrayOwnership { get; private set; }
/// <summary>
/// Gets the count of "relevant" elements. Usually be smaller than 'Array.Length' when <see cref="Array"/> is pooled.
/// Gets the count of "relevant" elements. It's usually smaller than 'Array.Length' when <see cref="Array"/> is pooled.
/// </summary>
public int Count { get; private set; }
public int Length { get; private set; }
/// <summary>
/// Gets the backing pinned array.
@ -100,13 +100,35 @@ namespace ImageSharp
public IntPtr Pointer { get; private set; }
/// <summary>
/// Converts <see cref="PinnedBuffer{T}"/> to an <see cref="BufferPointer{T}"/>.
/// Gets a <see cref="BufferSpan{T}"/> to the backing buffer.
/// </summary>
public BufferSpan<T> Span => this;
/// <summary>
/// Returns a reference to specified element of the buffer.
/// </summary>
/// <param name="index">The index</param>
/// <returns>The reference to the specified element</returns>
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<T>(index);
return ref Unsafe.AsRef<T>(ptr);
}
}
/// <summary>
/// Converts <see cref="PinnedBuffer{T}"/> to an <see cref="BufferSpan{T}"/>.
/// </summary>
/// <param name="buffer">The <see cref="PinnedBuffer{T}"/> to convert.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator BufferPointer<T>(PinnedBuffer<T> buffer)
public static unsafe implicit operator BufferSpan<T>(PinnedBuffer<T> buffer)
{
return buffer.Slice();
return new BufferSpan<T>(buffer.Array, (void*)buffer.Pointer, 0, buffer.Length);
}
/// <summary>
@ -114,6 +136,7 @@ namespace ImageSharp
/// </summary>
/// <param name="count">The desired count of elements. (Minimum size for <see cref="Array"/>)</param>
/// <returns>The <see cref="PinnedBuffer{T}"/> instance</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static PinnedBuffer<T> CreateClean(int count)
{
PinnedBuffer<T> buffer = new PinnedBuffer<T>(count);
@ -122,24 +145,26 @@ namespace ImageSharp
}
/// <summary>
/// Gets a <see cref="BufferPointer{T}"/> to the beginning of the raw data of the buffer.
/// Gets a <see cref="BufferSpan{T}"/> to an offseted position inside the buffer.
/// </summary>
/// <returns>The <see cref="BufferPointer{T}"/></returns>
/// <param name="start">The start</param>
/// <returns>The <see cref="BufferSpan{T}"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe BufferPointer<T> Slice()
public unsafe BufferSpan<T> Slice(int start)
{
return new BufferPointer<T>(this.Array, (void*)this.Pointer);
return new BufferSpan<T>(this.Array, (void*)this.Pointer, start, this.Length - start);
}
/// <summary>
/// Gets a <see cref="BufferPointer{T}"/> to an offseted position inside the buffer.
/// Gets a <see cref="BufferSpan{T}"/> to an offseted position inside the buffer.
/// </summary>
/// <param name="offset">The offset</param>
/// <returns>The <see cref="BufferPointer{T}"/></returns>
/// <param name="start">The start</param>
/// <param name="length">The length of the slice</param>
/// <returns>The <see cref="BufferSpan{T}"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe BufferPointer<T> Slice(int offset)
public unsafe BufferSpan<T> Slice(int start, int length)
{
return new BufferPointer<T>(this.Array, (void*)this.Pointer, offset);
return new BufferSpan<T>(this.Array, (void*)this.Pointer, start, length);
}
/// <summary>
@ -163,7 +188,7 @@ namespace ImageSharp
this.isPoolingOwner = false;
this.Array = null;
this.Count = 0;
this.Length = 0;
GC.SuppressFinalize(this);
}
@ -190,12 +215,12 @@ namespace ImageSharp
}
/// <summary>
/// Clears the buffer, filling elements between 0 and <see cref="Count"/>-1 with default(T)
/// Clears the buffer, filling elements between 0 and <see cref="Length"/>-1 with default(T)
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Clear()
{
this.Slice().Clear(this.Count);
((BufferSpan<T>)this).Clear();
}
/// <summary>

45
src/ImageSharp/Common/Memory/PinnedImageBufferExtensions.cs

@ -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);
}
}
}

78
src/ImageSharp/Common/Memory/PinnedImageBuffer{T}.cs

@ -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;
}
}
}

58
src/ImageSharp/Image/PixelAccessor{TColor}.cs

@ -15,7 +15,7 @@ namespace ImageSharp
/// Provides per-pixel access to generic <see cref="Image{TColor}"/> pixels.
/// </summary>
/// <typeparam name="TColor">The pixel format.</typeparam>
public sealed unsafe class PixelAccessor<TColor> : IDisposable
public sealed unsafe class PixelAccessor<TColor> : IDisposable, IPinnedImageBuffer<TColor>
where TColor : struct, IPixel<TColor>
{
/// <summary>
@ -37,7 +37,7 @@ namespace ImageSharp
/// <summary>
/// The <see cref="PinnedBuffer{T}"/> containing the pixel data.
/// </summary>
private PinnedBuffer<TColor> pixelBuffer;
private PinnedImageBuffer<TColor> pixelBuffer;
/// <summary>
/// Initializes a new instance of the <see cref="PixelAccessor{TColor}"/> class.
@ -59,7 +59,7 @@ namespace ImageSharp
/// <param name="width">The width of the image represented by the pixel buffer.</param>
/// <param name="height">The height of the image represented by the pixel buffer.</param>
public PixelAccessor(int width, int height)
: this(width, height, PinnedBuffer<TColor>.CreateClean(width * height))
: this(width, height, PinnedImageBuffer<TColor>.CreateClean(width, height))
{
}
@ -69,7 +69,7 @@ namespace ImageSharp
/// <param name="width">The width of the image represented by the pixel buffer.</param>
/// <param name="height">The height of the image represented by the pixel buffer.</param>
/// <param name="pixels">The pixel buffer.</param>
private PixelAccessor(int width, int height, PinnedBuffer<TColor> pixels)
private PixelAccessor(int width, int height, PinnedImageBuffer<TColor> pixels)
{
Guard.NotNull(pixels, nameof(pixels));
Guard.MustBeGreaterThan(width, 0, nameof(width));
@ -123,6 +123,9 @@ namespace ImageSharp
/// </summary>
public ParallelOptions ParallelOptions { get; }
/// <inheritdoc />
BufferSpan<TColor> IPinnedImageBuffer<TColor>.Span => this.pixelBuffer;
private static BulkPixelOperations<TColor> Operations => BulkPixelOperations<TColor>.Instance;
/// <summary>
@ -238,17 +241,6 @@ namespace ImageSharp
this.CopyTo(area, sourceX, sourceY, width, height);
}
/// <summary>
/// Gets a <see cref="BufferPointer{TColor}"/> to the row 'y' beginning from the pixel at 'x'.
/// </summary>
/// <param name="x">The x coordinate</param>
/// <param name="y">The y coordinate</param>
/// <returns>The <see cref="BufferPointer{TColor}"/></returns>
internal BufferPointer<TColor> GetRowPointer(int x, int y)
{
return this.pixelBuffer.Slice((y * this.Width) + x);
}
/// <summary>
/// Sets the pixel buffer in an unsafe manner. This should not be used unless you know what its doing!!!
/// </summary>
@ -288,8 +280,8 @@ namespace ImageSharp
{
for (int y = 0; y < height; y++)
{
BufferPointer<byte> source = area.GetRowPointer(y);
BufferPointer<TColor> destination = this.GetRowPointer(targetX, targetY + y);
BufferSpan<byte> source = area.GetRowSpan(y);
BufferSpan<TColor> destination = this.GetRowSpan(targetX, targetY + y);
Operations.PackFromZyxBytes(source, destination, width);
}
@ -308,8 +300,8 @@ namespace ImageSharp
{
for (int y = 0; y < height; y++)
{
BufferPointer<byte> source = area.GetRowPointer(y);
BufferPointer<TColor> destination = this.GetRowPointer(targetX, targetY + y);
BufferSpan<byte> source = area.GetRowSpan(y);
BufferSpan<TColor> destination = this.GetRowSpan(targetX, targetY + y);
Operations.PackFromZyxwBytes(source, destination, width);
}
@ -328,8 +320,8 @@ namespace ImageSharp
{
for (int y = 0; y < height; y++)
{
BufferPointer<byte> source = area.GetRowPointer(y);
BufferPointer<TColor> destination = this.GetRowPointer(targetX, targetY + y);
BufferSpan<byte> source = area.GetRowSpan(y);
BufferSpan<TColor> destination = this.GetRowSpan(targetX, targetY + y);
Operations.PackFromXyzBytes(source, destination, width);
}
@ -348,8 +340,8 @@ namespace ImageSharp
{
for (int y = 0; y < height; y++)
{
BufferPointer<byte> source = area.GetRowPointer(y);
BufferPointer<TColor> destination = this.GetRowPointer(targetX, targetY + y);
BufferSpan<byte> source = area.GetRowSpan(y);
BufferSpan<TColor> destination = this.GetRowSpan(targetX, targetY + y);
Operations.PackFromXyzwBytes(source, destination, width);
}
}
@ -367,8 +359,8 @@ namespace ImageSharp
{
for (int y = 0; y < height; y++)
{
BufferPointer<TColor> source = this.GetRowPointer(sourceX, sourceY + y);
BufferPointer<byte> destination = area.GetRowPointer(y);
BufferSpan<TColor> source = this.GetRowSpan(sourceX, sourceY + y);
BufferSpan<byte> destination = area.GetRowSpan(y);
Operations.ToZyxBytes(source, destination, width);
}
}
@ -386,8 +378,8 @@ namespace ImageSharp
{
for (int y = 0; y < height; y++)
{
BufferPointer<TColor> source = this.GetRowPointer(sourceX, sourceY + y);
BufferPointer<byte> destination = area.GetRowPointer(y);
BufferSpan<TColor> source = this.GetRowSpan(sourceX, sourceY + y);
BufferSpan<byte> destination = area.GetRowSpan(y);
Operations.ToZyxwBytes(source, destination, width);
}
}
@ -405,8 +397,8 @@ namespace ImageSharp
{
for (int y = 0; y < height; y++)
{
BufferPointer<TColor> source = this.GetRowPointer(sourceX, sourceY + y);
BufferPointer<byte> destination = area.GetRowPointer(y);
BufferSpan<TColor> source = this.GetRowSpan(sourceX, sourceY + y);
BufferSpan<byte> destination = area.GetRowSpan(y);
Operations.ToXyzBytes(source, destination, width);
}
}
@ -424,15 +416,15 @@ namespace ImageSharp
{
for (int y = 0; y < height; y++)
{
BufferPointer<TColor> source = this.GetRowPointer(sourceX, sourceY + y);
BufferPointer<byte> destination = area.GetRowPointer(y);
BufferSpan<TColor> source = this.GetRowSpan(sourceX, sourceY + y);
BufferSpan<byte> destination = area.GetRowSpan(y);
Operations.ToXyzwBytes(source, destination, width);
}
}
private void SetPixelBufferUnsafe(int width, int height, TColor[] pixels)
{
this.SetPixelBufferUnsafe(width, height, new PinnedBuffer<TColor>(width * height, pixels));
this.SetPixelBufferUnsafe(width, height, new PinnedImageBuffer<TColor>(pixels, width, height));
}
/// <summary>
@ -441,7 +433,7 @@ namespace ImageSharp
/// <param name="width">The width.</param>
/// <param name="height">The height.</param>
/// <param name="pixels">The pixel buffer</param>
private void SetPixelBufferUnsafe(int width, int height, PinnedBuffer<TColor> pixels)
private void SetPixelBufferUnsafe(int width, int height, PinnedImageBuffer<TColor> pixels)
{
this.pixelBuffer = pixels;
this.pixelsBase = (byte*)pixels.Pointer;

6
src/ImageSharp/Image/PixelArea{TColor}.cs

@ -202,11 +202,11 @@ namespace ImageSharp
}
/// <summary>
/// Gets a <see cref="BufferPointer{Byte}"/> to the row y.
/// Gets a <see cref="BufferSpan{T}"/> to the row y.
/// </summary>
/// <param name="y">The y coordinate</param>
/// <returns>The <see cref="BufferPointer{Byte}"/></returns>
internal BufferPointer<byte> GetRowPointer(int y)
/// <returns>The <see cref="BufferSpan{T}"/></returns>
internal BufferSpan<byte> GetRowSpan(int y)
{
return this.byteBuffer.Slice(y * this.RowStride);
}

1
src/ImageSharp/MetaData/Profiles/ICC/Enums/IccProfileTag.cs

@ -3,6 +3,7 @@
// Licensed under the Apache License, Version 2.0.
// </copyright>
// ReSharper disable InconsistentNaming
namespace ImageSharp
{
/// <summary>

171
src/ImageSharp/Processing/Processors/Transforms/CompandingResizeProcessor.cs

@ -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);
}
}
}
}

176
src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.Weights.cs

@ -0,0 +1,176 @@
namespace ImageSharp.Processing.Processors
{
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
/// <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);
}
}
}
}

93
src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.cs

@ -6,13 +6,15 @@
namespace ImageSharp.Processing.Processors
{
using System;
using System.Buffers;
using System.Runtime.InteropServices;
/// <summary>
/// Provides methods that allow the resizing of images using various algorithms.
/// Adapted from <see href="http://www.realtimerendering.com/resources/GraphicsGems/gemsiii/filter_rcg.c"/>
/// </summary>
/// <typeparam name="TColor">The pixel format.</typeparam>
internal abstract class ResamplingWeightedProcessor<TColor> : ImageProcessor<TColor>
internal abstract partial class ResamplingWeightedProcessor<TColor> : ImageProcessor<TColor>
where TColor : struct, IPixel<TColor>
{
/// <summary>
@ -59,32 +61,21 @@ namespace ImageSharp.Processing.Processors
/// <summary>
/// Gets or sets the horizontal weights.
/// </summary>
protected Weights[] HorizontalWeights { get; set; }
protected WeightsBuffer HorizontalWeights { get; set; }
/// <summary>
/// Gets or sets the vertical weights.
/// </summary>
protected Weights[] VerticalWeights { get; set; }
/// <inheritdoc/>
protected override void BeforeApply(ImageBase<TColor> source, Rectangle sourceRectangle)
{
if (!(this.Sampler is NearestNeighborResampler))
{
this.HorizontalWeights = this.PrecomputeWeights(this.ResizeRectangle.Width, sourceRectangle.Width);
this.VerticalWeights = this.PrecomputeWeights(this.ResizeRectangle.Height, sourceRectangle.Height);
}
}
protected WeightsBuffer VerticalWeights { get; set; }
/// <summary>
/// Computes the weights to apply at each pixel when resizing.
/// </summary>
/// <param name="destinationSize">The destination section size.</param>
/// <param name="sourceSize">The source section size.</param>
/// <returns>
/// The <see cref="T:Weights[]"/>.
/// </returns>
protected Weights[] PrecomputeWeights(int destinationSize, int sourceSize)
/// <param name="destinationSize">The destination size</param>
/// <param name="sourceSize">The source size</param>
/// <returns>The <see cref="WeightsBuffer"/></returns>
// TODO: Made internal to simplify experimenting with weights data. Make it protected again when finished figuring out how to optimize all the stuff!
internal unsafe WeightsBuffer PrecomputeWeights(int destinationSize, int sourceSize)
{
float ratio = (float)sourceSize / destinationSize;
float scale = ratio;
@ -96,7 +87,7 @@ namespace ImageSharp.Processing.Processors
IResampler sampler = this.Sampler;
float radius = (float)Math.Ceiling(scale * sampler.Radius);
Weights[] result = new Weights[destinationSize];
WeightsBuffer result = new WeightsBuffer(sourceSize, destinationSize);
for (int i = 0; i < destinationSize; i++)
{
@ -116,67 +107,55 @@ namespace ImageSharp.Processing.Processors
}
float sum = 0;
result[i] = new Weights();
Weight[] weights = new Weight[right - left + 1];
WeightsWindow ws = result.GetWeightsWindow(i, left, right);
result.Weights[i] = ws;
float* weights = ws.Ptr;
for (int j = left; j <= right; j++)
{
float weight = sampler.GetValue((j - center) / scale);
sum += weight;
weights[j - left] = new Weight(j, weight);
weights[j - left] = weight;
}
// Normalise, best to do it here rather than in the pixel loop later on.
if (sum > 0)
{
for (int w = 0; w < weights.Length; w++)
for (int w = 0; w < ws.Length; w++)
{
weights[w].Value = weights[w].Value / sum;
weights[w] = weights[w] / sum;
}
}
result[i].Values = weights;
}
return result;
}
/// <summary>
/// Represents the weight to be added to a scaled pixel.
/// </summary>
protected struct Weight
/// <inheritdoc/>
protected override void BeforeApply(ImageBase<TColor> source, Rectangle sourceRectangle)
{
/// <summary>
/// Initializes a new instance of the <see cref="Weight"/> struct.
/// </summary>
/// <param name="index">The index.</param>
/// <param name="value">The value.</param>
public Weight(int index, float value)
if (!(this.Sampler is NearestNeighborResampler))
{
this.Index = index;
this.Value = value;
}
/// <summary>
/// Gets the pixel index.
/// </summary>
public int Index { get; }
this.HorizontalWeights = this.PrecomputeWeights(
this.ResizeRectangle.Width,
sourceRectangle.Width);
/// <summary>
/// Gets or sets the result of the interpolation algorithm.
/// </summary>
public float Value { get; set; }
this.VerticalWeights = this.PrecomputeWeights(
this.ResizeRectangle.Height,
sourceRectangle.Height);
}
}
/// <summary>
/// Represents a collection of weights and their sum.
/// </summary>
protected class Weights
/// <inheritdoc />
protected override void AfterApply(ImageBase<TColor> source, Rectangle sourceRectangle)
{
/// <summary>
/// Gets or sets the values.
/// </summary>
public Weight[] Values { get; set; }
base.AfterApply(source, sourceRectangle);
this.HorizontalWeights?.Dispose();
this.HorizontalWeights = null;
this.VerticalWeights?.Dispose();
this.VerticalWeights = null;
}
}
}

85
src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs

@ -12,9 +12,6 @@ namespace ImageSharp.Processing.Processors
/// <summary>
/// Provides methods that allow the resizing of images using various algorithms.
/// </summary>
/// <remarks>
/// This version and the <see cref="CompandingResizeProcessor{TColor}"/> have been separated out to improve performance.
/// </remarks>
/// <typeparam name="TColor">The pixel format.</typeparam>
internal class ResizeProcessor<TColor> : ResamplingWeightedProcessor<TColor>
where TColor : struct, IPixel<TColor>
@ -45,7 +42,7 @@ namespace ImageSharp.Processing.Processors
}
/// <inheritdoc/>
protected override void OnApply(ImageBase<TColor> source, Rectangle sourceRectangle)
protected override unsafe 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)
@ -104,36 +101,49 @@ namespace ImageSharp.Processing.Processors
// A 2-pass 1D algorithm appears to be faster than splitting a 1-pass 2D algorithm
// First process the columns. Since we are not using multiple threads startY and endY
// are the upper and lower bounds of the source rectangle.
// TODO: Using a transposed variant of 'firstPassPixels' could eliminate the need for the WeightsWindow.ComputeWeightedColumnSum() method, and improve speed!
using (PixelAccessor<TColor> targetPixels = new PixelAccessor<TColor>(width, height))
{
using (PixelAccessor<TColor> sourcePixels = source.Lock())
using (PixelAccessor<TColor> firstPassPixels = new PixelAccessor<TColor>(width, source.Height))
using (PinnedImageBuffer<Vector4> firstPassPixels = new PinnedImageBuffer<Vector4>(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<Vector4> tempRowBuffer = new PinnedBuffer<Vector4>(sourcePixels.Width))
{
Weight xw = horizontalValues[i];
destination += sourcePixels[xw.Index + sourceX, y].ToVector4() * xw.Value;
BufferSpan<TColor> sourceRow = sourcePixels.GetRowSpan(y);
BulkPixelOperations<TColor>.Instance.ToVector4(
sourceRow,
tempRowBuffer,
sourceRow.Length);
if (this.Compand)
{
for (int x = minX; x < maxX; x++)
{
WeightsWindow window = this.HorizontalWeights.Weights[x - startX];
firstPassPixels[x, y] = window.ComputeExpandedWeightedRowSum(tempRowBuffer);
}
}
else
{
for (int x = minX; x < maxX; x++)
{
WeightsWindow window = this.HorizontalWeights.Weights[x - startX];
firstPassPixels[x, y] = window.ComputeWeightedRowSum(tempRowBuffer);
}
}
}
TColor d = default(TColor);
d.PackFromVector4(destination);
firstPassPixels[x, y] = d;
}
});
});
// Now process the rows.
Parallel.For(
@ -143,22 +153,31 @@ namespace ImageSharp.Processing.Processors
y =>
{
// Ensure offsets are normalised for cropping and padding.
Weight[] verticalValues = this.VerticalWeights[y - startY].Values;
WeightsWindow window = this.VerticalWeights.Weights[y - startY];
for (int x = 0; x < width; x++)
if (this.Compand)
{
// Destination color components
Vector4 destination = Vector4.Zero;
for (int i = 0; i < verticalValues.Length; i++)
for (int x = 0; x < width; x++)
{
Weight yw = verticalValues[i];
destination += firstPassPixels[x, yw.Index + sourceY].ToVector4() * yw.Value;
// Destination color components
Vector4 destination = window.ComputeWeightedColumnSum(firstPassPixels, x);
destination = destination.Compress();
TColor d = default(TColor);
d.PackFromVector4(destination);
targetPixels[x, y] = d;
}
}
else
{
for (int x = 0; x < width; x++)
{
// Destination color components
Vector4 destination = window.ComputeWeightedColumnSum(firstPassPixels, x);
TColor d = default(TColor);
d.PackFromVector4(destination);
targetPixels[x, y] = d;
TColor d = default(TColor);
d.PackFromVector4(destination);
targetPixels[x, y] = d;
}
}
});
}

12
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<TColor> processor;
if (compand)
{
processor = new CompandingResizeProcessor<TColor>(sampler, width, height, targetRectangle);
}
else
{
processor = new ResizeProcessor<TColor>(sampler, width, height, targetRectangle);
}
ResizeProcessor<TColor> processor =
new ResizeProcessor<TColor>(sampler, width, height, targetRectangle) { Compand = compand };
source.ApplyProcessor(processor, sourceRectangle);
return source;

9
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());

8
tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs

@ -314,8 +314,8 @@ namespace ImageSharp.Tests.Colors
public PinnedBuffer<TDest> ActualDestBuffer { get; }
public PinnedBuffer<TDest> ExpectedDestBuffer { get; }
public BufferPointer<TSource> Source => this.SourceBuffer.Slice();
public BufferPointer<TDest> ActualDest => this.ActualDestBuffer.Slice();
public BufferSpan<TSource> Source => this.SourceBuffer;
public BufferSpan<TDest> ActualDest => this.ActualDestBuffer;
public TestBuffers(TSource[] source, TDest[] expectedDest)
{
@ -335,7 +335,7 @@ namespace ImageSharp.Tests.Colors
public void Verify()
{
int count = this.ExpectedDestBuffer.Count;
int count = this.ExpectedDestBuffer.Length;
if (typeof(TDest) == typeof(Vector4))
{
@ -364,7 +364,7 @@ namespace ImageSharp.Tests.Colors
internal static void TestOperation<TSource, TDest>(
TSource[] source,
TDest[] expected,
Action<BufferPointer<TSource>, BufferPointer<TDest>> action)
Action<BufferSpan<TSource>, BufferSpan<TDest>> action)
where TSource : struct
where TDest : struct
{

276
tests/ImageSharp.Tests/Common/BufferPointerTests.cs → tests/ImageSharp.Tests/Common/BufferSpanTests.cs

@ -7,131 +7,133 @@ namespace ImageSharp.Tests.Common
using Xunit;
public unsafe class BufferPointerTests
using static TestStructs;
public unsafe class BufferSpanTests
{
public struct Foo
[Fact]
public void AsBytes()
{
public int A;
public double B;
Foo[] fooz = { new Foo(1, 2), new Foo(3, 4), new Foo(5, 6) };
public Foo(int a, double b)
using (PinnedBuffer<Foo> colorBuf = new PinnedBuffer<Foo>(fooz))
{
this.A = a;
this.B = b;
}
BufferSpan<Foo> orig = colorBuf.Slice(1);
BufferSpan<byte> asBytes = (BufferSpan < byte > )orig;
internal static Foo[] CreateArray(int size)
{
Foo[] result = new Foo[size];
for (int i = 0; i < size; i++)
{
result[i] = new Foo(i+1, i+1);
}
return result;
Assert.Equal(asBytes.Start, sizeof(Foo));
Assert.Equal(orig.PointerAtOffset, asBytes.PointerAtOffset);
}
}
/// <summary>
/// sizeof(AlignedFoo) == sizeof(long)
/// </summary>
public struct AlignedFoo
public class Construct
{
public int A;
public int B;
static AlignedFoo()
[Fact]
public void Basic()
{
Assert.Equal(sizeof(AlignedFoo), sizeof(long));
}
Foo[] array = Foo.CreateArray(3);
fixed (Foo* p = array)
{
// Act:
BufferSpan<Foo> span = new BufferSpan<Foo>(array, p);
public AlignedFoo(int a, int b)
{
this.A = a;
this.B = b;
// Assert:
Assert.Equal(array, span.Array);
Assert.Equal((IntPtr)p, span.PointerAtOffset);
Assert.Equal(3, span.Length);
}
}
internal static AlignedFoo[] CreateArray(int size)
[Fact]
public void WithStart()
{
AlignedFoo[] result = new AlignedFoo[size];
for (int i = 0; i < size; i++)
Foo[] array = Foo.CreateArray(4);
int start = 2;
fixed (Foo* p = array)
{
result[i] = new AlignedFoo(i + 1, i + 1);
// Act:
BufferSpan<Foo> span = new BufferSpan<Foo>(array, p, start);
// Assert:
Assert.Equal(array, span.Array);
Assert.Equal(start, span.Start);
Assert.Equal((IntPtr)(p + start), span.PointerAtOffset);
Assert.Equal(array.Length - start, span.Length);
}
return result;
}
}
[Fact]
public void AsBytes()
{
Foo[] fooz = { new Foo(1, 2), new Foo(3, 4), new Foo(5, 6) };
using (PinnedBuffer<Foo> colorBuf = new PinnedBuffer<Foo>(fooz))
[Fact]
public void WithStartAndLength()
{
BufferPointer<Foo> orig = colorBuf.Slice(1);
BufferPointer<byte> asBytes = (BufferPointer < byte > )orig;
Assert.Equal(asBytes.Offset, sizeof(Foo));
Assert.Equal(orig.PointerAtOffset, asBytes.PointerAtOffset);
Foo[] array = Foo.CreateArray(10);
int start = 2;
int length = 3;
fixed (Foo* p = array)
{
// Act:
BufferSpan<Foo> span = new BufferSpan<Foo>(array, p, start, length);
// Assert:
Assert.Equal(array, span.Array);
Assert.Equal(start, span.Start);
Assert.Equal((IntPtr)(p + start), span.PointerAtOffset);
Assert.Equal(length, span.Length);
}
}
}
[Fact]
public void ConstructWithoutOffset()
public class Slice
{
Foo[] array = Foo.CreateArray(3);
fixed (Foo* p = array)
[Fact]
public void StartOnly()
{
// Act:
BufferPointer<Foo> ap = new BufferPointer<Foo>(array, p);
Foo[] array = Foo.CreateArray(5);
int start0 = 2;
int start1 = 2;
int totalOffset = start0 + start1;
// Assert:
Assert.Equal(array, ap.Array);
Assert.Equal((IntPtr)p, ap.PointerAtOffset);
}
}
fixed (Foo* p = array)
{
BufferSpan<Foo> span = new BufferSpan<Foo>(array, p, start0);
[Fact]
public void ConstructWithOffset()
{
Foo[] array = Foo.CreateArray(3);
int offset = 2;
fixed (Foo* p = array)
{
// Act:
BufferPointer<Foo> ap = new BufferPointer<Foo>(array, p, offset);
// Act:
span = span.Slice(start1);
// Assert:
Assert.Equal(array, ap.Array);
Assert.Equal(offset, ap.Offset);
Assert.Equal((IntPtr)(p+offset), ap.PointerAtOffset);
// Assert:
Assert.Equal(array, span.Array);
Assert.Equal(totalOffset, span.Start);
Assert.Equal((IntPtr)(p + totalOffset), span.PointerAtOffset);
Assert.Equal(array.Length - totalOffset, span.Length);
}
}
}
[Fact]
public void Slice()
{
Foo[] array = Foo.CreateArray(5);
int offset0 = 2;
int offset1 = 2;
int totalOffset = offset0 + offset1;
fixed (Foo* p = array)
[Fact]
public void StartAndLength()
{
BufferPointer<Foo> ap = new BufferPointer<Foo>(array, p, offset0);
Foo[] array = Foo.CreateArray(10);
int start0 = 2;
int start1 = 2;
int totalOffset = start0 + start1;
int sliceLength = 3;
// Act:
ap = ap.Slice(offset1);
fixed (Foo* p = array)
{
BufferSpan<Foo> span = new BufferSpan<Foo>(array, p, start0);
// Assert:
Assert.Equal(array, ap.Array);
Assert.Equal(totalOffset, ap.Offset);
Assert.Equal((IntPtr)(p + totalOffset), ap.PointerAtOffset);
// Act:
span = span.Slice(start1, sliceLength);
// Assert:
Assert.Equal(array, span.Array);
Assert.Equal(totalOffset, span.Start);
Assert.Equal((IntPtr)(p + totalOffset), span.PointerAtOffset);
Assert.Equal(sliceLength, span.Length);
}
}
}
[Theory]
[InlineData(4)]
[InlineData(1500)]
@ -142,7 +144,7 @@ namespace ImageSharp.Tests.Common
int offset = 2;
fixed (Foo* p = array)
{
BufferPointer<Foo> ap = new BufferPointer<Foo>(array, p, offset);
BufferSpan<Foo> ap = new BufferSpan<Foo>(array, p, offset);
// Act:
ap.Clear(count);
@ -155,6 +157,51 @@ namespace ImageSharp.Tests.Common
}
public class Indexer
{
public static readonly TheoryData<int, int, int> IndexerData =
new TheoryData<int, int, int>()
{
{ 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<Foo> span = new BufferSpan<Foo>(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<Foo> span = new BufferSpan<Foo>(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>(T[] data, int idx)
@ -194,10 +241,10 @@ namespace ImageSharp.Tests.Common
fixed (Foo* pSource = source)
fixed (Foo* pDest = dest)
{
BufferPointer<Foo> apSource = new BufferPointer<Foo>(source, pSource, 1);
BufferPointer<Foo> apDest = new BufferPointer<Foo>(dest, pDest, 1);
BufferSpan<Foo> apSource = new BufferSpan<Foo>(source, pSource, 1);
BufferSpan<Foo> apDest = new BufferSpan<Foo>(dest, pDest, 1);
BufferPointer.Copy(apSource, apDest, count-1);
BufferSpan.Copy(apSource, apDest, count-1);
}
AssertNotDefault(source, 1);
@ -221,10 +268,10 @@ namespace ImageSharp.Tests.Common
fixed (AlignedFoo* pSource = source)
fixed (AlignedFoo* pDest = dest)
{
BufferPointer<AlignedFoo> apSource = new BufferPointer<AlignedFoo>(source, pSource, 1);
BufferPointer<AlignedFoo> apDest = new BufferPointer<AlignedFoo>(dest, pDest, 1);
BufferSpan<AlignedFoo> apSource = new BufferSpan<AlignedFoo>(source, pSource, 1);
BufferSpan<AlignedFoo> apDest = new BufferSpan<AlignedFoo>(dest, pDest, 1);
BufferPointer.Copy(apSource, apDest, count - 1);
BufferSpan.Copy(apSource, apDest, count - 1);
}
AssertNotDefault(source, 1);
@ -248,10 +295,10 @@ namespace ImageSharp.Tests.Common
fixed (int* pSource = source)
fixed (int* pDest = dest)
{
BufferPointer<int> apSource = new BufferPointer<int>(source, pSource, 1);
BufferPointer<int> apDest = new BufferPointer<int>(dest, pDest, 1);
BufferSpan<int> apSource = new BufferSpan<int>(source, pSource, 1);
BufferSpan<int> apDest = new BufferSpan<int>(dest, pDest, 1);
BufferPointer.Copy(apSource, apDest, count -1);
BufferSpan.Copy(apSource, apDest, count -1);
}
AssertNotDefault(source, 1);
@ -276,10 +323,10 @@ namespace ImageSharp.Tests.Common
fixed (Foo* pSource = source)
fixed (byte* pDest = dest)
{
BufferPointer<Foo> apSource = new BufferPointer<Foo>(source, pSource, 1);
BufferPointer<byte> apDest = new BufferPointer<byte>(dest, pDest, sizeof(Foo));
BufferSpan<Foo> apSource = new BufferSpan<Foo>(source, pSource, 1);
BufferSpan<byte> apDest = new BufferSpan<byte>(dest, pDest, sizeof(Foo));
BufferPointer.Copy(apSource, apDest, count - 1);
BufferSpan.Copy(apSource, apDest, count - 1);
}
AssertNotDefault(source, 1);
@ -303,10 +350,10 @@ namespace ImageSharp.Tests.Common
fixed (AlignedFoo* pSource = source)
fixed (byte* pDest = dest)
{
BufferPointer<AlignedFoo> apSource = new BufferPointer<AlignedFoo>(source, pSource, 1);
BufferPointer<byte> apDest = new BufferPointer<byte>(dest, pDest, sizeof(AlignedFoo));
BufferSpan<AlignedFoo> apSource = new BufferSpan<AlignedFoo>(source, pSource, 1);
BufferSpan<byte> apDest = new BufferSpan<byte>(dest, pDest, sizeof(AlignedFoo));
BufferPointer.Copy(apSource, apDest, count - 1);
BufferSpan.Copy(apSource, apDest, count - 1);
}
AssertNotDefault(source, 1);
@ -330,10 +377,10 @@ namespace ImageSharp.Tests.Common
fixed (int* pSource = source)
fixed (byte* pDest = dest)
{
BufferPointer<int> apSource = new BufferPointer<int>(source, pSource);
BufferPointer<byte> apDest = new BufferPointer<byte>(dest, pDest);
BufferSpan<int> apSource = new BufferSpan<int>(source, pSource);
BufferSpan<byte> apDest = new BufferSpan<byte>(dest, pDest);
BufferPointer.Copy(apSource, apDest, count);
BufferSpan.Copy(apSource, apDest, count);
}
AssertNotDefault(source, 1);
@ -355,10 +402,10 @@ namespace ImageSharp.Tests.Common
fixed(byte* pSource = source)
fixed (Foo* pDest = dest)
{
BufferPointer<byte> apSource = new BufferPointer<byte>(source, pSource);
BufferPointer<Foo> apDest = new BufferPointer<Foo>(dest, pDest);
BufferSpan<byte> apSource = new BufferSpan<byte>(source, pSource);
BufferSpan<Foo> apDest = new BufferSpan<Foo>(dest, pDest);
BufferPointer.Copy(apSource, apDest, count);
BufferSpan.Copy(apSource, apDest, count);
}
AssertNotDefault(source, sizeof(Foo) + 1);
@ -378,17 +425,18 @@ namespace ImageSharp.Tests.Common
using (PinnedBuffer<Color> colorBuf = new PinnedBuffer<Color>(colors))
using (PinnedBuffer<byte> byteBuf = new PinnedBuffer<byte>(colors.Length*4))
{
BufferPointer.Copy<Color>(colorBuf, byteBuf, colorBuf.Count);
BufferSpan.Copy<Color>(colorBuf, byteBuf, colorBuf.Length);
byte[] a = byteBuf.Array;
for (int i = 0; i < byteBuf.Count; i++)
for (int i = 0; i < byteBuf.Length; i++)
{
Assert.Equal((byte)i, a[i]);
}
}
}
internal static bool ElementsAreEqual(Foo[] array, byte[] rawArray, int index)
{
fixed (Foo* pArray = array)

140
tests/ImageSharp.Tests/Common/PinnedBufferTests.cs

@ -7,15 +7,10 @@
using Xunit;
using static TestStructs;
public unsafe class PinnedBufferTests
{
public struct Foo
{
public int A;
public double B;
}
[Theory]
[InlineData(42)]
[InlineData(1111)]
@ -25,7 +20,7 @@
{
Assert.False(buffer.IsDisposedOrLostArrayOwnership);
Assert.NotNull(buffer.Array);
Assert.Equal(count, buffer.Count);
Assert.Equal(count, buffer.Length);
Assert.True(buffer.Array.Length >= count);
VerifyPointer(buffer);
@ -42,7 +37,7 @@
{
Assert.False(buffer.IsDisposedOrLostArrayOwnership);
Assert.Equal(array, buffer.Array);
Assert.Equal(count, buffer.Count);
Assert.Equal(count, buffer.Length);
VerifyPointer(buffer);
}
@ -66,18 +61,56 @@
[Fact]
public void CreateClean()
{
Parallel.For(0, 100,
i =>
for (int i = 0; i < 100; i++)
{
using (PinnedBuffer<int> buffer = PinnedBuffer<int>.CreateClean(42))
{
for (int j = 0; j < buffer.Length; j++)
{
using (PinnedBuffer<int> buffer = PinnedBuffer<int>.CreateClean(42))
{
for (int j = 0; j < buffer.Count; j++)
{
Assert.Equal(0, buffer.Array[j]);
buffer.Array[j] = 666;
}
}
});
Assert.Equal(0, buffer.Array[j]);
buffer.Array[j] = 666;
}
}
}
}
public class Indexer
{
public static readonly TheoryData<int, int> IndexerData =
new TheoryData<int,int>()
{
{ 10, 0 },
{ 16, 3 },
{ 10, 9 }
};
[Theory]
[MemberData(nameof(IndexerData))]
public void Read(int length, int index)
{
Foo[] a = Foo.CreateArray(length);
using (PinnedBuffer<Foo> buffer = new PinnedBuffer<Foo>(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<Foo> buffer = new PinnedBuffer<Foo>(a))
{
buffer[index] = new Foo(666, 666);
Assert.Equal(new Foo(666, 666), a[index]);
}
}
}
[Fact]
@ -89,21 +122,72 @@
Assert.True(buffer.IsDisposedOrLostArrayOwnership);
}
[Theory]
[InlineData(7)]
[InlineData(123)]
public void CastToSpan(int bufferLength)
{
using (PinnedBuffer<Foo> buffer = new PinnedBuffer<Foo>(bufferLength))
{
BufferSpan<Foo> 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<Foo> buffer = new PinnedBuffer<Foo>(a))
using (PinnedBuffer<Foo> buffer = new PinnedBuffer<Foo>(42))
{
BufferPointer<Foo> arrayPtr = buffer.Slice();
BufferSpan<Foo> span = buffer.Span;
Assert.Equal(a, arrayPtr.Array);
Assert.Equal(0, arrayPtr.Offset);
Assert.Equal(buffer.Pointer, arrayPtr.PointerAtOffset);
Assert.Equal(buffer.Array, span.Array);
Assert.Equal(0, span.Start);
Assert.Equal(buffer.Pointer, span.PointerAtOffset);
Assert.Equal(span.Length, 42);
}
}
public class Slice
{
[Theory]
[InlineData(7, 2)]
[InlineData(123, 17)]
public void WithStartOnly(int bufferLength, int start)
{
using (PinnedBuffer<Foo> buffer = new PinnedBuffer<Foo>(bufferLength))
{
BufferSpan<Foo> span = buffer.Slice(start);
Assert.Equal(buffer.Array, span.Array);
Assert.Equal(start, span.Start);
Assert.Equal(buffer.Pointer + start * Unsafe.SizeOf<Foo>(), 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<Foo> buffer = new PinnedBuffer<Foo>(bufferLength))
{
BufferSpan<Foo> span = buffer.Slice(start, spanLength);
Assert.Equal(buffer.Array, span.Array);
Assert.Equal(start, span.Start);
Assert.Equal(buffer.Pointer + start * Unsafe.SizeOf<Foo>(), span.PointerAtOffset);
Assert.Equal(span.Length, spanLength);
}
}
}
[Fact]
public void UnPinAndTakeArrayOwnership()
{

106
tests/ImageSharp.Tests/Common/PinnedImageBufferTests.cs

@ -0,0 +1,106 @@
// ReSharper disable InconsistentNaming
namespace ImageSharp.Tests.Common
{
using System.Runtime.CompilerServices;
using Xunit;
using static TestStructs;
public unsafe class PinnedImageBufferTests
{
[Theory]
[InlineData(7, 42)]
[InlineData(1025, 17)]
public void Construct(int width, int height)
{
using (PinnedImageBuffer<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));
}
}
}
}

62
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;
}
}
/// <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;
}
}
}
}

61
tests/ImageSharp.Tests/Processors/Filters/ResizeProfilingBenchmarks.cs

@ -0,0 +1,61 @@
namespace ImageSharp.Tests
{
using System.IO;
using System.Text;
using ImageSharp.Processing;
using ImageSharp.Processing.Processors;
using Xunit;
using Xunit.Abstractions;
public class ResizeProfilingBenchmarks : MeasureFixture
{
public ResizeProfilingBenchmarks(ITestOutputHelper output)
: base(output)
{
}
public int ExecutionCount { get; set; } = 50;
// [Theory] // Benchmark, enable manually!
[InlineData(100, 100)]
[InlineData(2000, 2000)]
public void ResizeBicubic(int width, int height)
{
this.Measure(this.ExecutionCount,
() =>
{
using (Image image = new Image(width, height))
{
image.Resize(width / 4, height / 4);
}
});
}
// [Fact]
public void PrintWeightsData()
{
ResizeProcessor<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…
Cancel
Save