Browse Source

Merge pull request #125 from JimBobSquarePants/PinnedBuffer

Introducing PinnedBuffer<T> to manage pinned (and optionally pooled) arrays as disposable resources + Introducing BulkedPixelOperations API.
pull/112/merge
Anton Firsov 9 years ago
committed by GitHub
parent
commit
e4bd08c02f
  1. 3
      src/ImageSharp/Colors/Color.cs
  2. 3
      src/ImageSharp/Colors/PackedPixel/Alpha8.cs
  3. 3
      src/ImageSharp/Colors/PackedPixel/Argb.cs
  4. 3
      src/ImageSharp/Colors/PackedPixel/Bgr565.cs
  5. 3
      src/ImageSharp/Colors/PackedPixel/Bgra4444.cs
  6. 3
      src/ImageSharp/Colors/PackedPixel/Bgra5551.cs
  7. 256
      src/ImageSharp/Colors/PackedPixel/BulkPixelOperations{TColor}.cs
  8. 3
      src/ImageSharp/Colors/PackedPixel/Byte4.cs
  9. 3
      src/ImageSharp/Colors/PackedPixel/HalfSingle.cs
  10. 3
      src/ImageSharp/Colors/PackedPixel/HalfVector2.cs
  11. 3
      src/ImageSharp/Colors/PackedPixel/HalfVector4.cs
  12. 6
      src/ImageSharp/Colors/PackedPixel/IPixel.cs
  13. 3
      src/ImageSharp/Colors/PackedPixel/NormalizedByte2.cs
  14. 3
      src/ImageSharp/Colors/PackedPixel/NormalizedByte4.cs
  15. 3
      src/ImageSharp/Colors/PackedPixel/NormalizedShort2.cs
  16. 3
      src/ImageSharp/Colors/PackedPixel/NormalizedShort4.cs
  17. 3
      src/ImageSharp/Colors/PackedPixel/Rg32.cs
  18. 3
      src/ImageSharp/Colors/PackedPixel/Rgba1010102.cs
  19. 3
      src/ImageSharp/Colors/PackedPixel/Rgba64.cs
  20. 3
      src/ImageSharp/Colors/PackedPixel/Short2.cs
  21. 3
      src/ImageSharp/Colors/PackedPixel/Short4.cs
  22. 29
      src/ImageSharp/Common/Extensions/ArrayExtensions.cs
  23. 91
      src/ImageSharp/Common/Memory/BufferPointer.cs
  24. 45
      src/ImageSharp/Common/Memory/BufferPointer{T}.cs
  25. 164
      src/ImageSharp/Common/Memory/PinnedBuffer{T}.cs
  26. 61
      src/ImageSharp/Common/Memory/PixelDataPool{T}.cs
  27. 7
      src/ImageSharp/Image/ImageBase{TColor}.cs
  28. 108
      src/ImageSharp/Image/PixelAccessor{TColor}.cs
  29. 101
      src/ImageSharp/Image/PixelArea{TColor}.cs
  30. 42
      src/ImageSharp/Image/PixelPool{TColor}.cs
  31. 129
      tests/ImageSharp.Benchmarks/Color/Bulk/PixelAccessorVirtualCopy.cs
  32. 2
      tests/ImageSharp.Benchmarks/Drawing/DrawBeziers.cs
  33. 2
      tests/ImageSharp.Benchmarks/Drawing/DrawLines.cs
  34. 2
      tests/ImageSharp.Benchmarks/Drawing/DrawPolygon.cs
  35. 2
      tests/ImageSharp.Benchmarks/Drawing/FillWithPattern.cs
  36. 25
      tests/ImageSharp.Benchmarks/General/ArrayCopy.cs
  37. 54
      tests/ImageSharp.Benchmarks/General/Vectorization/BitwiseOrUint32.cs
  38. 54
      tests/ImageSharp.Benchmarks/General/Vectorization/DivFloat.cs
  39. 54
      tests/ImageSharp.Benchmarks/General/Vectorization/DivUInt32.cs
  40. 54
      tests/ImageSharp.Benchmarks/General/Vectorization/MulFloat.cs
  41. 54
      tests/ImageSharp.Benchmarks/General/Vectorization/MulUInt32.cs
  42. 62
      tests/ImageSharp.Benchmarks/General/Vectorization/ReinterpretUInt32AsFloat.cs
  43. 17
      tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj
  44. 16
      tests/ImageSharp.Sandbox46/Program.cs
  45. 4
      tests/ImageSharp.Sandbox46/app.config
  46. 1
      tests/ImageSharp.Sandbox46/packages.config
  47. 340
      tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs
  48. 83
      tests/ImageSharp.Tests/Common/ArrayPointerTests.cs
  49. 167
      tests/ImageSharp.Tests/Common/BufferPointerTests.cs
  50. 94
      tests/ImageSharp.Tests/Common/PinnedBufferTests.cs
  51. 93
      tests/ImageSharp.Tests/Common/PixelDataPoolTests.cs
  52. 74
      tests/ImageSharp.Tests/Image/PixelPoolTests.cs

3
src/ImageSharp/Colors/Color.cs

@ -245,6 +245,9 @@ namespace ImageSharp
return ColorBuilder<Color>.FromHex(hex);
}
/// <inheritdoc />
public BulkPixelOperations<Color> CreateBulkOperations() => new BulkPixelOperations<Color>();
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void PackFromBytes(byte x, byte y, byte z, byte w)

3
src/ImageSharp/Colors/PackedPixel/Alpha8.cs

@ -58,6 +58,9 @@ namespace ImageSharp
return left.PackedValue != right.PackedValue;
}
/// <inheritdoc />
public BulkPixelOperations<Alpha8> CreateBulkOperations() => new BulkPixelOperations<Alpha8>();
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void PackFromVector4(Vector4 vector)

3
src/ImageSharp/Colors/PackedPixel/Argb.cs

@ -220,6 +220,9 @@ namespace ImageSharp
this.PackedValue = Pack(ref vector);
}
/// <inheritdoc />
public BulkPixelOperations<Argb> CreateBulkOperations() => new BulkPixelOperations<Argb>();
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vector4 ToVector4()

3
src/ImageSharp/Colors/PackedPixel/Bgr565.cs

@ -67,6 +67,9 @@ namespace ImageSharp
return left.PackedValue != right.PackedValue;
}
/// <inheritdoc />
public BulkPixelOperations<Bgr565> CreateBulkOperations() => new BulkPixelOperations<Bgr565>();
/// <summary>
/// Expands the packed representation into a <see cref="Vector3"/>.
/// The vector components are typically expanded in least to greatest significance order.

3
src/ImageSharp/Colors/PackedPixel/Bgra4444.cs

@ -66,6 +66,9 @@ namespace ImageSharp
return left.PackedValue != right.PackedValue;
}
/// <inheritdoc />
public BulkPixelOperations<Bgra4444> CreateBulkOperations() => new BulkPixelOperations<Bgra4444>();
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vector4 ToVector4()

3
src/ImageSharp/Colors/PackedPixel/Bgra5551.cs

@ -68,6 +68,9 @@ namespace ImageSharp
return left.PackedValue != right.PackedValue;
}
/// <inheritdoc />
public BulkPixelOperations<Bgra5551> CreateBulkOperations() => new BulkPixelOperations<Bgra5551>();
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vector4 ToVector4()

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

@ -0,0 +1,256 @@
// <copyright file="BulkPixelOperations{TColor}.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.Numerics;
using System.Runtime.CompilerServices;
/// <summary>
/// A stateless class implementing Strategy Pattern for batched pixel-data conversion operations
/// for pixel buffers of type <typeparamref name="TColor"/>.
/// </summary>
/// <typeparam name="TColor">The pixel format.</typeparam>
public unsafe class BulkPixelOperations<TColor>
where TColor : struct, IPixel<TColor>
{
/// <summary>
/// The size of <typeparamref name="TColor"/> in bytes
/// </summary>
private static readonly int ColorSize = Unsafe.SizeOf<TColor>();
/// <summary>
/// Gets the global <see cref="BulkPixelOperations{TColor}"/> instance for the pixel type <typeparamref name="TColor"/>
/// </summary>
public static BulkPixelOperations<TColor> Instance { get; } = default(TColor).CreateBulkOperations();
/// <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="count">The number of pixels to convert.</param>
internal virtual void PackFromVector4(
BufferPointer<Vector4> sourceVectors,
BufferPointer<TColor> destColors,
int count)
{
Vector4* sp = (Vector4*)sourceVectors.PointerAtOffset;
byte* dp = (byte*)destColors;
for (int i = 0; i < count; i++)
{
Vector4 v = Unsafe.Read<Vector4>(sp);
TColor c = default(TColor);
c.PackFromVector4(v);
Unsafe.Write(dp, c);
sp++;
dp += ColorSize;
}
}
/// <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="count">The number of pixels to convert.</param>
internal virtual void ToVector4(
BufferPointer<TColor> sourceColors,
BufferPointer<Vector4> destVectors,
int count)
{
byte* sp = (byte*)sourceColors;
Vector4* dp = (Vector4*)destVectors.PointerAtOffset;
for (int i = 0; i < count; i++)
{
TColor c = Unsafe.Read<TColor>(sp);
*dp = c.ToVector4();
sp += ColorSize;
dp++;
}
}
/// <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="count">The number of pixels to convert.</param>
internal virtual void PackFromXyzBytes(
BufferPointer<byte> sourceBytes,
BufferPointer<TColor> destColors,
int count)
{
byte* sp = (byte*)sourceBytes;
byte* dp = (byte*)destColors.PointerAtOffset;
for (int i = 0; i < count; i++)
{
TColor c = default(TColor);
c.PackFromBytes(sp[0], sp[1], sp[2], 255);
Unsafe.Write(dp, c);
sp += 3;
dp += ColorSize;
}
}
/// <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="count">The number of pixels to convert.</param>
internal virtual void ToXyzBytes(BufferPointer<TColor> sourceColors, BufferPointer<byte> destBytes, int count)
{
byte* sp = (byte*)sourceColors;
byte[] dest = destBytes.Array;
for (int i = destBytes.Offset; i < destBytes.Offset + (count * 3); i += 3)
{
TColor c = Unsafe.Read<TColor>(sp);
c.ToXyzBytes(dest, i);
sp += ColorSize;
}
}
/// <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="count">The number of pixels to convert.</param>
internal virtual void PackFromXyzwBytes(
BufferPointer<byte> sourceBytes,
BufferPointer<TColor> destColors,
int count)
{
byte* sp = (byte*)sourceBytes;
byte* dp = (byte*)destColors.PointerAtOffset;
for (int i = 0; i < count; i++)
{
TColor c = default(TColor);
c.PackFromBytes(sp[0], sp[1], sp[2], sp[3]);
Unsafe.Write(dp, c);
sp += 4;
dp += ColorSize;
}
}
/// <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="count">The number of pixels to convert.</param>
internal virtual void ToXyzwBytes(
BufferPointer<TColor> sourceColors,
BufferPointer<byte> destBytes,
int count)
{
byte* sp = (byte*)sourceColors;
byte[] dest = destBytes.Array;
for (int i = destBytes.Offset; i < destBytes.Offset + (count * 4); i += 4)
{
TColor c = Unsafe.Read<TColor>(sp);
c.ToXyzwBytes(dest, i);
sp += ColorSize;
}
}
/// <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="count">The number of pixels to convert.</param>
internal virtual void PackFromZyxBytes(
BufferPointer<byte> sourceBytes,
BufferPointer<TColor> destColors,
int count)
{
byte* sp = (byte*)sourceBytes;
byte* dp = (byte*)destColors.PointerAtOffset;
for (int i = 0; i < count; i++)
{
TColor c = default(TColor);
c.PackFromBytes(sp[2], sp[1], sp[0], 255);
Unsafe.Write(dp, c);
sp += 3;
dp += ColorSize;
}
}
/// <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="count">The number of pixels to convert.</param>
internal virtual void ToZyxBytes(BufferPointer<TColor> sourceColors, BufferPointer<byte> destBytes, int count)
{
byte* sp = (byte*)sourceColors;
byte[] dest = destBytes.Array;
for (int i = destBytes.Offset; i < destBytes.Offset + (count * 3); i += 3)
{
TColor c = Unsafe.Read<TColor>(sp);
c.ToZyxBytes(dest, i);
sp += ColorSize;
}
}
/// <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="count">The number of pixels to convert.</param>
internal virtual void PackFromZyxwBytes(
BufferPointer<byte> sourceBytes,
BufferPointer<TColor> destColors,
int count)
{
byte* sp = (byte*)sourceBytes;
byte* dp = (byte*)destColors.PointerAtOffset;
for (int i = 0; i < count; i++)
{
TColor c = default(TColor);
c.PackFromBytes(sp[2], sp[1], sp[0], sp[3]);
Unsafe.Write(dp, c);
sp += 4;
dp += ColorSize;
}
}
/// <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="count">The number of pixels to convert.</param>
internal virtual void ToZyxwBytes(
BufferPointer<TColor> sourceColors,
BufferPointer<byte> destBytes,
int count)
{
byte* sp = (byte*)sourceColors;
byte[] dest = destBytes.Array;
for (int i = destBytes.Offset; i < destBytes.Offset + (count * 4); i += 4)
{
TColor c = Unsafe.Read<TColor>(sp);
c.ToZyxwBytes(dest, i);
sp += ColorSize;
}
}
}
}

3
src/ImageSharp/Colors/PackedPixel/Byte4.cs

@ -69,6 +69,9 @@ namespace ImageSharp
return left.PackedValue != right.PackedValue;
}
/// <inheritdoc />
public BulkPixelOperations<Byte4> CreateBulkOperations() => new BulkPixelOperations<Byte4>();
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void PackFromVector4(Vector4 vector)

3
src/ImageSharp/Colors/PackedPixel/HalfSingle.cs

@ -72,6 +72,9 @@ namespace ImageSharp
return left.PackedValue != right.PackedValue;
}
/// <inheritdoc />
public BulkPixelOperations<HalfSingle> CreateBulkOperations() => new BulkPixelOperations<HalfSingle>();
/// <summary>
/// Expands the packed representation into a <see cref="float"/>.
/// </summary>

3
src/ImageSharp/Colors/PackedPixel/HalfVector2.cs

@ -82,6 +82,9 @@ namespace ImageSharp
return !left.Equals(right);
}
/// <inheritdoc />
public BulkPixelOperations<HalfVector2> CreateBulkOperations() => new BulkPixelOperations<HalfVector2>();
/// <summary>
/// Expands the packed representation into a <see cref="Vector2"/>.
/// </summary>

3
src/ImageSharp/Colors/PackedPixel/HalfVector4.cs

@ -85,6 +85,9 @@ namespace ImageSharp
return !left.Equals(right);
}
/// <inheritdoc />
public BulkPixelOperations<HalfVector4> CreateBulkOperations() => new BulkPixelOperations<HalfVector4>();
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void PackFromVector4(Vector4 vector)

6
src/ImageSharp/Colors/PackedPixel/IPixel.cs

@ -15,6 +15,12 @@ namespace ImageSharp
public interface IPixel<TSelf> : IPixel, IEquatable<TSelf>
where TSelf : struct, IPixel<TSelf>
{
/// <summary>
/// Creates a <see cref="BulkPixelOperations{TColor}"/> instance for this pixel type.
/// This method is not intended to be consumed directly. Use <see cref="BulkPixelOperations{TColor}.Instance"/> instead.
/// </summary>
/// <returns>The <see cref="BulkPixelOperations{TColor}"/> instance.</returns>
BulkPixelOperations<TSelf> CreateBulkOperations();
}
/// <summary>

3
src/ImageSharp/Colors/PackedPixel/NormalizedByte2.cs

@ -87,6 +87,9 @@ namespace ImageSharp
return left.PackedValue != right.PackedValue;
}
/// <inheritdoc />
public BulkPixelOperations<NormalizedByte2> CreateBulkOperations() => new BulkPixelOperations<NormalizedByte2>();
/// <summary>
/// Expands the packed representation into a <see cref="Vector2"/>.
/// The vector components are typically expanded in least to greatest significance order.

3
src/ImageSharp/Colors/PackedPixel/NormalizedByte4.cs

@ -89,6 +89,9 @@ namespace ImageSharp
return left.PackedValue != right.PackedValue;
}
/// <inheritdoc />
public BulkPixelOperations<NormalizedByte4> CreateBulkOperations() => new BulkPixelOperations<NormalizedByte4>();
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void PackFromVector4(Vector4 vector)

3
src/ImageSharp/Colors/PackedPixel/NormalizedShort2.cs

@ -87,6 +87,9 @@ namespace ImageSharp
return !left.Equals(right);
}
/// <inheritdoc />
public BulkPixelOperations<NormalizedShort2> CreateBulkOperations() => new BulkPixelOperations<NormalizedShort2>();
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void PackFromVector4(Vector4 vector)

3
src/ImageSharp/Colors/PackedPixel/NormalizedShort4.cs

@ -89,6 +89,9 @@ namespace ImageSharp
return !left.Equals(right);
}
/// <inheritdoc />
public BulkPixelOperations<NormalizedShort4> CreateBulkOperations() => new BulkPixelOperations<NormalizedShort4>();
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void PackFromVector4(Vector4 vector)

3
src/ImageSharp/Colors/PackedPixel/Rg32.cs

@ -72,6 +72,9 @@ namespace ImageSharp
return left.PackedValue != right.PackedValue;
}
/// <inheritdoc />
public BulkPixelOperations<Rg32> CreateBulkOperations() => new BulkPixelOperations<Rg32>();
/// <summary>
/// Expands the packed representation into a <see cref="Vector2"/>.
/// The vector components are typically expanded in least to greatest significance order.

3
src/ImageSharp/Colors/PackedPixel/Rgba1010102.cs

@ -75,6 +75,9 @@ namespace ImageSharp
return left.PackedValue != right.PackedValue;
}
/// <inheritdoc />
public BulkPixelOperations<Rgba1010102> CreateBulkOperations() => new BulkPixelOperations<Rgba1010102>();
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vector4 ToVector4()

3
src/ImageSharp/Colors/PackedPixel/Rgba64.cs

@ -74,6 +74,9 @@ namespace ImageSharp
return left.PackedValue != right.PackedValue;
}
/// <inheritdoc />
public BulkPixelOperations<Rgba64> CreateBulkOperations() => new BulkPixelOperations<Rgba64>();
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vector4 ToVector4()

3
src/ImageSharp/Colors/PackedPixel/Short2.cs

@ -87,6 +87,9 @@ namespace ImageSharp
return left.PackedValue != right.PackedValue;
}
/// <inheritdoc />
public BulkPixelOperations<Short2> CreateBulkOperations() => new BulkPixelOperations<Short2>();
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void PackFromVector4(Vector4 vector)

3
src/ImageSharp/Colors/PackedPixel/Short4.cs

@ -89,6 +89,9 @@ namespace ImageSharp
return left.PackedValue != right.PackedValue;
}
/// <inheritdoc />
public BulkPixelOperations<Short4> CreateBulkOperations() => new BulkPixelOperations<Short4>();
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void PackFromVector4(Vector4 vector)

29
src/ImageSharp/Common/Extensions/ArrayExtensions.cs

@ -1,29 +0,0 @@
// <copyright file="ArrayExtensions.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;
/// <summary>
/// Extension methods for arrays.
/// </summary>
public static class ArrayExtensions
{
/// <summary>
/// Locks the pixel buffer providing access to the pixels.
/// </summary>
/// <typeparam name="TColor">The pixel format.</typeparam>
/// <param name="pixels">The pixel buffer.</param>
/// <param name="width">Gets 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>
/// <returns>The <see cref="PixelAccessor{TColor}"/></returns>
public static PixelAccessor<TColor> Lock<TColor>(this TColor[] pixels, int width, int height)
where TColor : struct, IPixel<TColor>
{
return new PixelAccessor<TColor>(width, height, pixels);
}
}
}

91
src/ImageSharp/Common/Memory/BufferPointer.cs

@ -0,0 +1,91 @@
// <copyright file="BufferPointer.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.Runtime.CompilerServices;
/// <summary>
/// Utility methods for <see cref="BufferPointer{T}"/>
/// </summary>
internal static class BufferPointer
{
/// <summary>
/// Gets a <see cref="BufferPointer{T}"/> to the beginning of the raw data in 'buffer'.
/// </summary>
/// <typeparam name="T">The element type</typeparam>
/// <param name="buffer">The input <see cref="PinnedBuffer{T}"/></param>
/// <returns>The <see cref="BufferPointer{T}"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe BufferPointer<T> Slice<T>(this PinnedBuffer<T> buffer)
where T : struct
{
return new BufferPointer<T>(buffer.Array, (void*)buffer.Pointer);
}
/// <summary>
/// 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="count">The number of elements to copy</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe void Copy<T>(BufferPointer<T> source, BufferPointer<T> destination, int count)
where T : struct
{
Unsafe.CopyBlock((void*)source.PointerAtOffset, (void*)destination.PointerAtOffset, USizeOf<T>(count));
}
/// <summary>
/// Copy 'countInSource' elements of <typeparamref name="T"/> from 'source' into the raw byte buffer 'destination'.
/// </summary>
/// <typeparam name="T">The element type.</typeparam>
/// <param name="source">The source buffer of <typeparamref name="T"/> elements to copy from.</param>
/// <param name="destination">The destination buffer.</param>
/// <param name="countInSource">The number of elements to copy from 'source'</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe void Copy<T>(BufferPointer<T> source, BufferPointer<byte> destination, int countInSource)
where T : struct
{
Unsafe.CopyBlock((void*)source.PointerAtOffset, (void*)destination.PointerAtOffset, USizeOf<T>(countInSource));
}
/// <summary>
/// Copy 'countInDest' number of <typeparamref name="T"/> elements into 'dest' from a raw byte buffer defined by 'source'.
/// </summary>
/// <typeparam name="T">The element type.</typeparam>
/// <param name="source">The raw source buffer to copy from"/></param>
/// <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)
where T : struct
{
Unsafe.CopyBlock((void*)source.PointerAtOffset, (void*)destination.PointerAtOffset, USizeOf<T>(countInDest));
}
/// <summary>
/// Gets the size of `count` elements in bytes.
/// </summary>
/// <typeparam name="T">The element type.</typeparam>
/// <param name="count">The count of the elements</param>
/// <returns>The size in bytes as int</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int SizeOf<T>(int count)
where T : struct => Unsafe.SizeOf<T>() * count;
/// <summary>
/// Gets the size of `count` elements in bytes as UInt32
/// </summary>
/// <typeparam name="T">The element type.</typeparam>
/// <param name="count">The count of the elements</param>
/// <returns>The size in bytes as UInt32</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static uint USizeOf<T>(int count)
where T : struct
=> (uint)SizeOf<T>(count);
}
}

45
src/ImageSharp/Common/Memory/ArrayPointer{T}.cs → src/ImageSharp/Common/Memory/BufferPointer{T}.cs

@ -1,4 +1,4 @@
// <copyright file="ArrayPointer{T}.cs" company="James Jackson-South">
// <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>
@ -15,21 +15,21 @@ namespace ImageSharp
/// - 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="ArrayPointer{T}"/> an unsafe type!
/// - Currently the arrays provided to ArrayPointer need to be pinned. This behaviour could be changed using C#7 features.
/// 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 ArrayPointer<T>
internal unsafe struct BufferPointer<T>
where T : struct
{
/// <summary>
/// Initializes a new instance of the <see cref="ArrayPointer{T}"/> struct from a pinned array and an offset.
/// 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 ArrayPointer(T[] array, void* pointerToArray, int offset)
public BufferPointer(T[] array, void* pointerToArray, int offset)
{
DebugGuard.NotNull(array, nameof(array));
@ -39,12 +39,12 @@ namespace ImageSharp
}
/// <summary>
/// Initializes a new instance of the <see cref="ArrayPointer{T}"/> struct from a pinned array.
/// 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 ArrayPointer(T[] array, void* pointerToArray)
public BufferPointer(T[] array, void* pointerToArray)
{
DebugGuard.NotNull(array, nameof(array));
@ -69,13 +69,34 @@ namespace ImageSharp
public IntPtr PointerAtOffset { get; private set; }
/// <summary>
/// Forms a slice out of the given ArrayPointer, beginning at 'offset'.
/// 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>
/// Convertes <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>
/// 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) ArrayPointer</returns>
public ArrayPointer<T> Slice(int offset)
/// <returns>The offseted (sliced) BufferPointer</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public BufferPointer<T> Slice(int offset)
{
ArrayPointer<T> result = default(ArrayPointer<T>);
BufferPointer<T> result = default(BufferPointer<T>);
result.Array = this.Array;
result.Offset = this.Offset + offset;
result.PointerAtOffset = this.PointerAtOffset + (Unsafe.SizeOf<T>() * offset);

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

@ -0,0 +1,164 @@
// <copyright file="PinnedBuffer{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.Buffers;
using System.Runtime.InteropServices;
/// <summary>
/// Manages a pinned buffer of value type data 'T' as a Disposable resource.
/// The backing array is either pooled or comes from the outside.
/// </summary>
/// <typeparam name="T">The value type.</typeparam>
internal class PinnedBuffer<T> : IDisposable
where T : struct
{
/// <summary>
/// A handle that allows to access the managed <see cref="Array"/> as an unmanaged memory by pinning.
/// </summary>
private GCHandle handle;
/// <summary>
/// A value indicating whether this <see cref="PinnedBuffer{T}"/> instance should return the array to the pool.
/// </summary>
private bool isPoolingOwner;
/// <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)
{
this.Count = count;
this.Array = PixelDataPool<T>.Rent(count);
this.isPoolingOwner = true;
this.Pin();
}
/// <summary>
/// Initializes a new instance of the <see cref="PinnedBuffer{T}"/> class.
/// </summary>
/// <param name="array">The array to pin.</param>
public PinnedBuffer(T[] array)
{
this.Count = array.Length;
this.Array = array;
this.Pin();
}
/// <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)
{
if (array.Length < count)
{
throw new ArgumentException("Can't initialize a PinnedBuffer with array.Length < count", nameof(array));
}
this.Count = count;
this.Array = array;
this.Pin();
}
/// <summary>
/// Finalizes an instance of the <see cref="PinnedBuffer{T}"/> class.
/// </summary>
~PinnedBuffer()
{
this.UnPin();
}
/// <summary>
/// Gets a value indicating whether this <see cref="PinnedBuffer{T}"/> instance is disposed, or has lost ownership of <see cref="Array"/>.
/// </summary>
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.
/// </summary>
public int Count { get; private set; }
/// <summary>
/// Gets the backing pinned array.
/// </summary>
public T[] Array { get; private set; }
/// <summary>
/// Gets a pointer to the pinned <see cref="Array"/>.
/// </summary>
public IntPtr Pointer { get; private set; }
/// <summary>
/// Disposes the <see cref="PinnedBuffer{T}"/> instance by unpinning the array, and returning the pooled buffer when necessary.
/// </summary>
public void Dispose()
{
if (this.IsDisposedOrLostArrayOwnership)
{
return;
}
this.IsDisposedOrLostArrayOwnership = true;
this.UnPin();
if (this.isPoolingOwner)
{
PixelDataPool<T>.Return(this.Array);
}
this.Array = null;
this.Count = 0;
GC.SuppressFinalize(this);
}
/// <summary>
/// Unpins <see cref="Array"/> and makes the object "quasi-disposed" so the array is no longer owned by this object.
/// If <see cref="Array"/> is rented, it's the callers responsibility to return it to it's pool. (Most likely <see cref="PixelDataPool{T}"/>)
/// </summary>
/// <returns>The unpinned <see cref="Array"/></returns>
public T[] UnPinAndTakeArrayOwnership()
{
if (this.IsDisposedOrLostArrayOwnership)
{
throw new InvalidOperationException("UnPinAndTakeArrayOwnership() is invalid: either PinnedBuffer<T> is disposed or UnPinAndTakeArrayOwnership() has been called multiple times!");
}
this.IsDisposedOrLostArrayOwnership = true;
this.UnPin();
T[] array = this.Array;
this.Array = null;
return array;
}
/// <summary>
/// Pins <see cref="Array"/>.
/// </summary>
private void Pin()
{
this.handle = GCHandle.Alloc(this.Array, GCHandleType.Pinned);
this.Pointer = this.handle.AddrOfPinnedObject();
}
/// <summary>
/// Unpins <see cref="Array"/>.
/// </summary>
private void UnPin()
{
if (this.Pointer == IntPtr.Zero || !this.handle.IsAllocated)
{
return;
}
this.handle.Free();
this.Pointer = IntPtr.Zero;
}
}
}

61
src/ImageSharp/Common/Memory/PixelDataPool{T}.cs

@ -0,0 +1,61 @@
// <copyright file="PixelDataPool{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.Buffers;
/// <summary>
/// Provides a resource pool that enables reusing instances of value type arrays <see cref="T:T[]"/>.
/// <see cref="Rent(int)"/> will always return arrays initialized with 'default(T)'
/// </summary>
/// <typeparam name="T">The value type.</typeparam>
public static class PixelDataPool<T>
where T : struct
{
/// <summary>
/// The <see cref="ArrayPool{T}"/> used to pool data.
/// </summary>
private static readonly ArrayPool<T> ArrayPool = ArrayPool<T>.Create(CalculateMaxArrayLength(), 50);
/// <summary>
/// Rents the pixel array from the pool.
/// </summary>
/// <param name="minimumLength">The minimum length of the array to return.</param>
/// <returns>The <see cref="T:TColor[]"/></returns>
public static T[] Rent(int minimumLength)
{
return ArrayPool.Rent(minimumLength);
}
/// <summary>
/// Returns the rented pixel array back to the pool.
/// </summary>
/// <param name="array">The array to return to the buffer pool.</param>
public static void Return(T[] array)
{
ArrayPool.Return(array, true);
}
/// <summary>
/// Heuristically calculates a reasonable maxArrayLength value for the backing <see cref="ArrayPool"/>.
/// </summary>
/// <returns>The maxArrayLength value</returns>
internal static int CalculateMaxArrayLength()
{
// ReSharper disable once SuspiciousTypeConversion.Global
if (default(T) is IPixel)
{
const int MaximumExpectedImageSize = 16384;
return MaximumExpectedImageSize * MaximumExpectedImageSize;
}
else
{
return int.MaxValue;
}
}
}
}

7
src/ImageSharp/Image/ImageBase{TColor}.cs

@ -162,13 +162,12 @@ namespace ImageSharp
internal void SwapPixelsBuffers(PixelAccessor<TColor> pixelSource)
{
Guard.NotNull(pixelSource, nameof(pixelSource));
Guard.IsTrue(pixelSource.PooledMemory, nameof(pixelSource.PooledMemory), "pixelSource must be using pooled memory");
int newWidth = pixelSource.Width;
int newHeight = pixelSource.Height;
// Push my memory into the accessor (which in turn unpins the old puffer ready for the images use)
TColor[] newPixels = pixelSource.ReturnCurrentPixelsAndReplaceThemInternally(this.Width, this.Height, this.pixelBuffer, true);
TColor[] newPixels = pixelSource.ReturnCurrentPixelsAndReplaceThemInternally(this.Width, this.Height, this.pixelBuffer);
this.Width = newWidth;
this.Height = newHeight;
this.pixelBuffer = newPixels;
@ -222,7 +221,7 @@ namespace ImageSharp
/// </summary>
private void RentPixels()
{
this.pixelBuffer = PixelPool<TColor>.RentPixels(this.Width * this.Height);
this.pixelBuffer = PixelDataPool<TColor>.Rent(this.Width * this.Height);
}
/// <summary>
@ -230,7 +229,7 @@ namespace ImageSharp
/// </summary>
private void ReturnPixels()
{
PixelPool<TColor>.ReturnPixels(this.pixelBuffer);
PixelDataPool<TColor>.Return(this.pixelBuffer);
this.pixelBuffer = null;
}
}

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

@ -18,21 +18,11 @@ namespace ImageSharp
public unsafe class PixelAccessor<TColor> : IDisposable
where TColor : struct, IPixel<TColor>
{
/// <summary>
/// The pointer to the pixel buffer.
/// </summary>
private IntPtr dataPointer;
/// <summary>
/// The position of the first pixel in the image.
/// </summary>
private byte* pixelsBase;
/// <summary>
/// Provides a way to access the pixels from unmanaged memory.
/// </summary>
private GCHandle pixelsHandle;
/// <summary>
/// A value indicating whether this instance of the given entity has been disposed.
/// </summary>
@ -45,9 +35,9 @@ namespace ImageSharp
private bool isDisposed;
/// <summary>
/// The pixel buffer
/// The <see cref="PinnedBuffer{T}"/> containing the pixel data.
/// </summary>
private TColor[] pixelBuffer;
private PinnedBuffer<TColor> pixelBuffer;
/// <summary>
/// Initializes a new instance of the <see cref="PixelAccessor{TColor}"/> class.
@ -59,28 +49,17 @@ namespace ImageSharp
Guard.MustBeGreaterThan(image.Width, 0, "image width");
Guard.MustBeGreaterThan(image.Height, 0, "image height");
this.SetPixelBufferUnsafe(image.Width, image.Height, image.Pixels, false);
this.SetPixelBufferUnsafe(image.Width, image.Height, image.Pixels);
this.ParallelOptions = image.Configuration.ParallelOptions;
}
/// <summary>
/// Initializes a new instance of the <see cref="PixelAccessor{TColor}"/> class.
/// </summary>
/// <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>
public PixelAccessor(int width, int height, TColor[] pixels)
: this(width, height, pixels, false)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="PixelAccessor{TColor}"/> class.
/// </summary>
/// <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, PixelPool<TColor>.RentPixels(width * height), true)
: this(width, height, new PinnedBuffer<TColor>(width * height))
{
}
@ -90,19 +69,13 @@ 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>
/// <param name="pooledMemory">if set to <c>true</c> then the <see cref="T:TColor[]"/> is from the <see cref="PixelPool{TColor}"/> thus should be returned once disposed.</param>
private PixelAccessor(int width, int height, TColor[] pixels, bool pooledMemory)
private PixelAccessor(int width, int height, PinnedBuffer<TColor> pixels)
{
Guard.NotNull(pixels, nameof(pixels));
Guard.MustBeGreaterThan(width, 0, nameof(width));
Guard.MustBeGreaterThan(height, 0, nameof(height));
if (!(pixels.Length >= width * height))
{
throw new ArgumentException($"Pixel array must have the length of at least {width * height}.");
}
this.SetPixelBufferUnsafe(width, height, pixels, pooledMemory);
this.SetPixelBufferUnsafe(width, height, pixels);
this.ParallelOptions = Configuration.Default.ParallelOptions;
}
@ -116,14 +89,14 @@ namespace ImageSharp
}
/// <summary>
/// Gets a value indicating whether the current pixel buffer is from a pooled source.
/// Gets the pixel buffer array.
/// </summary>
public bool PooledMemory { get; private set; }
public TColor[] PixelBuffer => this.pixelBuffer.Array;
/// <summary>
/// Gets the pointer to the pixel buffer.
/// </summary>
public IntPtr DataPointer => this.dataPointer;
public IntPtr DataPointer => this.pixelBuffer.Pointer;
/// <summary>
/// Gets the size of a single pixel in the number of bytes.
@ -242,23 +215,17 @@ namespace ImageSharp
return;
}
this.UnPinPixels();
// Note disposing is done.
this.isDisposed = true;
this.pixelBuffer.Dispose();
// This object will be cleaned up by the Dispose method.
// Therefore, you should call GC.SuppressFinalize to
// take this object off the finalization queue
// and prevent finalization code for this object
// from executing a second time.
GC.SuppressFinalize(this);
if (this.PooledMemory)
{
PixelPool<TColor>.ReturnPixels(this.pixelBuffer);
this.pixelBuffer = null;
}
}
/// <summary>
@ -275,13 +242,12 @@ namespace ImageSharp
/// <param name="width">The width.</param>
/// <param name="height">The height.</param>
/// <param name="pixels">The pixels.</param>
/// <param name="pooledMemory">If set to <c>true</c> this indicates that the pixel buffer is from a pooled source.</param>
/// <returns>Returns the old pixel data thats has gust been replaced.</returns>
/// <remarks>If <see cref="M:PixelAccessor.PooledMemory"/> is true then caller is responsible for ensuring <see cref="M:PixelPool.ReturnPixels()"/> is called.</remarks>
internal TColor[] ReturnCurrentPixelsAndReplaceThemInternally(int width, int height, TColor[] pixels, bool pooledMemory)
/// <remarks>If <see cref="M:PixelAccessor.PooledMemory"/> is true then caller is responsible for ensuring <see cref="M:PixelDataPool.Return()"/> is called.</remarks>
internal TColor[] ReturnCurrentPixelsAndReplaceThemInternally(int width, int height, TColor[] pixels)
{
TColor[] oldPixels = this.pixelBuffer;
this.SetPixelBufferUnsafe(width, height, pixels, pooledMemory);
TColor[] oldPixels = this.pixelBuffer.UnPinAndTakeArrayOwnership();
this.SetPixelBufferUnsafe(width, height, pixels);
return oldPixels;
}
@ -509,54 +475,28 @@ namespace ImageSharp
return this.pixelsBase + (((y * this.Width) + x) * Unsafe.SizeOf<TColor>());
}
private void SetPixelBufferUnsafe(int width, int height, TColor[] pixels)
{
this.SetPixelBufferUnsafe(width, height, new PinnedBuffer<TColor>(width * height, pixels));
}
/// <summary>
/// Sets the pixel buffer in an unsafe manor this should not be used unless you know what its doing!!!
/// </summary>
/// <param name="width">The width.</param>
/// <param name="height">The height.</param>
/// <param name="pixels">The pixels.</param>
/// <param name="pooledMemory">If set to <c>true</c> this indicates that the pixel buffer is from a pooled source.</param>
private void SetPixelBufferUnsafe(int width, int height, TColor[] pixels, bool pooledMemory)
/// <param name="pixels">The pixel buffer</param>
private void SetPixelBufferUnsafe(int width, int height, PinnedBuffer<TColor> pixels)
{
this.pixelBuffer = pixels;
this.PooledMemory = pooledMemory;
this.pixelsBase = (byte*)pixels.Pointer;
this.Width = width;
this.Height = height;
this.PinPixels();
this.PixelSize = Unsafe.SizeOf<TColor>();
this.RowStride = this.Width * this.PixelSize;
}
/// <summary>
/// Pins the pixels data.
/// </summary>
private void PinPixels()
{
// unpin any old pixels just incase
this.UnPinPixels();
this.pixelsHandle = GCHandle.Alloc(this.pixelBuffer, GCHandleType.Pinned);
this.dataPointer = this.pixelsHandle.AddrOfPinnedObject();
this.pixelsBase = (byte*)this.dataPointer.ToPointer();
}
/// <summary>
/// Unpins pixels data.
/// </summary>
private void UnPinPixels()
{
if (this.pixelsBase != null)
{
if (this.pixelsHandle.IsAllocated)
{
this.pixelsHandle.Free();
}
this.dataPointer = IntPtr.Zero;
this.pixelsBase = null;
}
}
/// <summary>
/// Copy an area of pixels to the image.
/// </summary>

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

@ -18,21 +18,6 @@ namespace ImageSharp
public sealed unsafe class PixelArea<TColor> : IDisposable
where TColor : struct, IPixel<TColor>
{
/// <summary>
/// True if <see cref="Bytes"/> was rented from <see cref="BytesPool"/> by the constructor
/// </summary>
private readonly bool isBufferRented;
/// <summary>
/// Provides a way to access the pixels from unmanaged memory.
/// </summary>
private readonly GCHandle pixelsHandle;
/// <summary>
/// The pointer to the pixel buffer.
/// </summary>
private IntPtr dataPointer;
/// <summary>
/// A value indicating whether this instance of the given entity has been disposed.
/// </summary>
@ -44,6 +29,11 @@ namespace ImageSharp
/// </remarks>
private bool isDisposed;
/// <summary>
/// The underlying buffer containing the raw pixel data.
/// </summary>
private PinnedBuffer<byte> byteBuffer;
/// <summary>
/// Initializes a new instance of the <see cref="PixelArea{TColor}"/> class.
/// </summary>
@ -76,14 +66,10 @@ namespace ImageSharp
this.Height = height;
this.ComponentOrder = componentOrder;
this.RowStride = width * GetComponentCount(componentOrder);
this.Bytes = bytes;
this.Length = bytes.Length;
this.isBufferRented = false;
this.pixelsHandle = GCHandle.Alloc(this.Bytes, GCHandleType.Pinned);
this.Length = bytes.Length; // TODO: Is this the right value for Length?
// TODO: Why is Resharper warning us about an impure method call?
this.dataPointer = this.pixelsHandle.AddrOfPinnedObject();
this.PixelBase = (byte*)this.dataPointer.ToPointer();
this.byteBuffer = new PinnedBuffer<byte>(bytes);
this.PixelBase = (byte*)this.byteBuffer.Pointer;
}
/// <summary>
@ -132,27 +118,15 @@ namespace ImageSharp
this.ComponentOrder = componentOrder;
this.RowStride = (width * GetComponentCount(componentOrder)) + padding;
this.Length = this.RowStride * height;
this.Bytes = BytesPool.Rent(this.Length);
this.isBufferRented = true;
this.pixelsHandle = GCHandle.Alloc(this.Bytes, GCHandleType.Pinned);
// TODO: Why is Resharper warning us about an impure method call?
this.dataPointer = this.pixelsHandle.AddrOfPinnedObject();
this.PixelBase = (byte*)this.dataPointer.ToPointer();
}
/// <summary>
/// Finalizes an instance of the <see cref="PixelArea{TColor}"/> class.
/// </summary>
~PixelArea()
{
this.Dispose(false);
this.byteBuffer = new PinnedBuffer<byte>(this.Length);
this.PixelBase = (byte*)this.byteBuffer.Pointer;
}
/// <summary>
/// Gets the data in bytes.
/// </summary>
public byte[] Bytes { get; }
public byte[] Bytes => this.byteBuffer.Array;
/// <summary>
/// Gets the length of the buffer.
@ -167,7 +141,7 @@ namespace ImageSharp
/// <summary>
/// Gets the pointer to the pixel buffer.
/// </summary>
public IntPtr DataPointer => this.dataPointer;
public IntPtr DataPointer => this.byteBuffer.Pointer;
/// <summary>
/// Gets the height.
@ -189,25 +163,18 @@ namespace ImageSharp
/// </summary>
public int Width { get; }
/// <summary>
/// Gets the pool used to rent bytes, when it's not coming from an external source.
/// </summary>
// TODO: Use own pool?
private static ArrayPool<byte> BytesPool => ArrayPool<byte>.Shared;
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
this.Dispose(true);
if (this.isDisposed)
{
return;
}
// This object will be cleaned up by the Dispose method.
// Therefore, you should call GC.SuppressFinalize to
// take this object off the finalization queue
// and prevent finalization code for this object
// from executing a second time.
GC.SuppressFinalize(this);
this.byteBuffer.Dispose();
this.isDisposed = true;
}
/// <summary>
@ -282,37 +249,5 @@ namespace ImageSharp
$"Invalid byte array length. Length {bytes.Length}; Should be {requiredLength}.");
}
}
/// <summary>
/// Disposes the object and frees resources for the Garbage Collector.
/// </summary>
/// <param name="disposing">If true, the object gets disposed.</param>
private void Dispose(bool disposing)
{
if (this.isDisposed)
{
return;
}
if (this.PixelBase == null)
{
return;
}
if (this.pixelsHandle.IsAllocated)
{
this.pixelsHandle.Free();
}
if (disposing && this.isBufferRented)
{
BytesPool.Return(this.Bytes);
}
this.dataPointer = IntPtr.Zero;
this.PixelBase = null;
this.isDisposed = true;
}
}
}

42
src/ImageSharp/Image/PixelPool{TColor}.cs

@ -1,42 +0,0 @@
// <copyright file="PixelPool{TColor}.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.Buffers;
/// <summary>
/// Provides a resource pool that enables reusing instances of type <see cref="T:TColor[]"/>.
/// </summary>
/// <typeparam name="TColor">The pixel format.</typeparam>
public static class PixelPool<TColor>
where TColor : struct, IPixel<TColor>
{
/// <summary>
/// The <see cref="ArrayPool{T}"/> used to pool data. TODO: Choose sensible default size and count
/// </summary>
private static readonly ArrayPool<TColor> ArrayPool = ArrayPool<TColor>.Create(int.MaxValue, 50);
/// <summary>
/// Rents the pixel array from the pool.
/// </summary>
/// <param name="minimumLength">The minimum length of the array to return.</param>
/// <returns>The <see cref="T:TColor[]"/></returns>
public static TColor[] RentPixels(int minimumLength)
{
return ArrayPool.Rent(minimumLength);
}
/// <summary>
/// Returns the rented pixel array back to the pool.
/// </summary>
/// <param name="array">The array to return to the buffer pool.</param>
public static void ReturnPixels(TColor[] array)
{
ArrayPool.Return(array, true);
}
}
}

129
tests/ImageSharp.Benchmarks/Color/Bulk/PixelAccessorVirtualCopy.cs

@ -0,0 +1,129 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace ImageSharp.Benchmarks.Color.Bulk
{
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using BenchmarkDotNet.Attributes;
using Color = ImageSharp.Color;
/// <summary>
/// Benchmark to measure the effect of using virtual bulk-copy calls inside PixelAccessor methods
/// </summary>
public unsafe class PixelAccessorVirtualCopy
{
abstract class CopyExecutor
{
internal abstract void VirtualCopy(BufferPointer<Color> destination, BufferPointer<byte> source, int count);
}
class UnsafeCopyExecutor : CopyExecutor
{
[MethodImpl(MethodImplOptions.NoInlining)]
internal override unsafe void VirtualCopy(BufferPointer<Color> destination, BufferPointer<byte> source, int count)
{
Unsafe.CopyBlock((void*)destination.PointerAtOffset, (void*)source.PointerAtOffset, (uint)count*4);
}
}
private PixelAccessor<Color> pixelAccessor;
private PixelArea<Color> area;
private CopyExecutor executor;
[Params(64, 256, 512)]
public int Width { get; set; }
public int Height { get; set; } = 256;
[Setup]
public void Setup()
{
this.pixelAccessor = new PixelAccessor<ImageSharp.Color>(this.Width, this.Height);
this.area = new PixelArea<Color>(this.Width / 2, this.Height, ComponentOrder.Xyzw);
this.executor = new UnsafeCopyExecutor();
}
[Cleanup]
public void Cleanup()
{
this.pixelAccessor.Dispose();
this.area.Dispose();
}
[Benchmark(Baseline = true)]
public void CopyRawUnsafeInlined()
{
uint byteCount = (uint)this.area.Width * 4;
int targetX = this.Width / 4;
int targetY = 0;
for (int y = 0; y < this.Height; y++)
{
byte* source = this.area.PixelBase + (y * this.area.RowStride);
byte* destination = this.GetRowPointer(targetX, targetY + y);
Unsafe.CopyBlock(destination, source, byteCount);
}
}
[Benchmark]
public void CopyBufferPointerUnsafeInlined()
{
uint byteCount = (uint)this.area.Width * 4;
int targetX = this.Width / 4;
int targetY = 0;
for (int y = 0; y < this.Height; y++)
{
BufferPointer<byte> source = this.GetAreaRow(y);
BufferPointer<Color> destination = this.GetPixelAccessorRow(targetX, targetY + y);
Unsafe.CopyBlock((void*)destination.PointerAtOffset, (void*)source.PointerAtOffset, byteCount);
}
}
[Benchmark]
public void CopyBufferPointerUnsafeVirtual()
{
int targetX = this.Width / 4;
int targetY = 0;
for (int y = 0; y < this.Height; y++)
{
BufferPointer<byte> source = this.GetAreaRow(y);
BufferPointer<Color> destination = this.GetPixelAccessorRow(targetX, targetY + y);
this.executor.VirtualCopy(destination, source, this.area.Width);
}
}
private byte* GetRowPointer(int x, int y)
{
return (byte*)this.pixelAccessor.DataPointer + (((y * this.pixelAccessor.Width) + x) * Unsafe.SizeOf<Color>());
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private BufferPointer<Color> GetPixelAccessorRow(int x, int y)
{
return new BufferPointer<ImageSharp.Color>(
this.pixelAccessor.PixelBuffer,
(void*)this.pixelAccessor.DataPointer,
(y * this.pixelAccessor.Width) + x
);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private BufferPointer<byte> GetAreaRow(int y)
{
return new BufferPointer<byte>(this.area.Bytes, this.area.PixelBase, y * this.area.RowStride);
}
}
}

2
tests/ImageSharp.Benchmarks/Drawing/DrawBeziers.cs

@ -28,7 +28,7 @@ namespace ImageSharp.Benchmarks
{
graphics.InterpolationMode = InterpolationMode.Default;
graphics.SmoothingMode = SmoothingMode.AntiAlias;
var pen = new Pen(Color.HotPink, 10);
var pen = new Pen(System.Drawing.Color.HotPink, 10);
graphics.DrawBeziers(pen, new[] {
new PointF(10, 500),
new PointF(30, 10),

2
tests/ImageSharp.Benchmarks/Drawing/DrawLines.cs

@ -28,7 +28,7 @@ namespace ImageSharp.Benchmarks
{
graphics.InterpolationMode = InterpolationMode.Default;
graphics.SmoothingMode = SmoothingMode.AntiAlias;
var pen = new Pen(Color.HotPink, 10);
var pen = new Pen(System.Drawing.Color.HotPink, 10);
graphics.DrawLines(pen, new[] {
new PointF(10, 10),
new PointF(550, 50),

2
tests/ImageSharp.Benchmarks/Drawing/DrawPolygon.cs

@ -27,7 +27,7 @@ namespace ImageSharp.Benchmarks
{
graphics.InterpolationMode = InterpolationMode.Default;
graphics.SmoothingMode = SmoothingMode.AntiAlias;
var pen = new Pen(Color.HotPink, 10);
var pen = new Pen(System.Drawing.Color.HotPink, 10);
graphics.DrawPolygon(pen, new[] {
new PointF(10, 10),
new PointF(550, 50),

2
tests/ImageSharp.Benchmarks/Drawing/FillWithPattern.cs

@ -25,7 +25,7 @@ namespace ImageSharp.Benchmarks
using (Graphics graphics = Graphics.FromImage(destination))
{
graphics.SmoothingMode = SmoothingMode.AntiAlias;
var brush = new HatchBrush(HatchStyle.BackwardDiagonal, Color.HotPink);
var brush = new HatchBrush(HatchStyle.BackwardDiagonal, System.Drawing.Color.HotPink);
graphics.FillRectangle(brush, new Rectangle(0,0, 800,800)); // can't find a way to flood fill with a brush
}
using (MemoryStream ms = new MemoryStream())

25
tests/ImageSharp.Benchmarks/General/ArrayCopy.cs

@ -2,22 +2,23 @@
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Benchmarks.General
{
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using BenchmarkDotNet.Attributes;
[Config(typeof(Config.Short))]
public class ArrayCopy
{
[Params(100, 1000, 10000)]
[Params(10, 100, 1000, 10000)]
public int Count { get; set; }
private byte[] source;
byte[] source;
private byte[] destination;
byte[] destination;
[Setup]
public void SetUp()
@ -42,6 +43,12 @@ namespace ImageSharp.Benchmarks.General
}
}
[Benchmark(Description = "Copy using Buffer.BlockCopy()")]
public void CopyUsingBufferBlockCopy()
{
Buffer.BlockCopy(this.source, 0, this.destination, 0, this.Count);
}
[Benchmark(Description = "Copy using Buffer.MemoryCopy<T>")]
public unsafe void CopyUsingBufferMemoryCopy()
{
@ -51,5 +58,15 @@ namespace ImageSharp.Benchmarks.General
Buffer.MemoryCopy(pinnedSource, pinnedDestination, this.Count, this.Count);
}
}
[Benchmark(Description = "Copy using Marshal.Copy<T>")]
public unsafe void CopyUsingMarshalCopy()
{
fixed (byte* pinnedDestination = this.destination)
{
Marshal.Copy(this.source, 0, (IntPtr)pinnedDestination, this.Count);
}
}
}
}

54
tests/ImageSharp.Benchmarks/General/Vectorization/BitwiseOrUint32.cs

@ -0,0 +1,54 @@
namespace ImageSharp.Benchmarks.General.Vectorization
{
using System.Numerics;
using BenchmarkDotNet.Attributes;
public class BitwiseOrUInt32
{
private uint[] input;
private uint[] result;
[Params(32)]
public int InputSize { get; set; }
private uint testValue;
[Setup]
public void Setup()
{
this.input = new uint[this.InputSize];
this.result = new uint[this.InputSize];
this.testValue = 42;
for (int i = 0; i < this.InputSize; i++)
{
this.input[i] = (uint) i;
}
}
[Benchmark(Baseline = true)]
public void Standard()
{
uint v = this.testValue;
for (int i = 0; i < this.input.Length; i++)
{
this.result[i] = this.input[i] | v;
}
}
[Benchmark]
public void Simd()
{
Vector<uint> v = new Vector<uint>(this.testValue);
for (int i = 0; i < this.input.Length; i+=Vector<uint>.Count)
{
Vector<uint> a = new Vector<uint>(this.input, i);
a = Vector.BitwiseOr(a, v);
a.CopyTo(this.result, i);
}
}
}
}

54
tests/ImageSharp.Benchmarks/General/Vectorization/DivFloat.cs

@ -0,0 +1,54 @@
namespace ImageSharp.Benchmarks.General.Vectorization
{
using System.Numerics;
using BenchmarkDotNet.Attributes;
public class DivFloat
{
private float[] input;
private float[] result;
[Params(32)]
public int InputSize { get; set; }
private float testValue;
[Setup]
public void Setup()
{
this.input = new float[this.InputSize];
this.result = new float[this.InputSize];
this.testValue = 42;
for (int i = 0; i < this.InputSize; i++)
{
this.input[i] = (uint)i;
}
}
[Benchmark(Baseline = true)]
public void Standard()
{
float v = this.testValue;
for (int i = 0; i < this.input.Length; i++)
{
this.result[i] = this.input[i] / v;
}
}
[Benchmark]
public void Simd()
{
Vector<float> v = new Vector<float>(this.testValue);
for (int i = 0; i < this.input.Length; i += Vector<uint>.Count)
{
Vector<float> a = new Vector<float>(this.input, i);
a = a / v;
a.CopyTo(this.result, i);
}
}
}
}

54
tests/ImageSharp.Benchmarks/General/Vectorization/DivUInt32.cs

@ -0,0 +1,54 @@
namespace ImageSharp.Benchmarks.General.Vectorization
{
using System.Numerics;
using BenchmarkDotNet.Attributes;
public class DivUInt32
{
private uint[] input;
private uint[] result;
[Params(32)]
public int InputSize { get; set; }
private uint testValue;
[Setup]
public void Setup()
{
this.input = new uint[this.InputSize];
this.result = new uint[this.InputSize];
this.testValue = 42;
for (int i = 0; i < this.InputSize; i++)
{
this.input[i] = (uint)i;
}
}
[Benchmark(Baseline = true)]
public void Standard()
{
uint v = this.testValue;
for (int i = 0; i < this.input.Length; i++)
{
this.result[i] = this.input[i] / v;
}
}
[Benchmark]
public void Simd()
{
Vector<uint> v = new Vector<uint>(this.testValue);
for (int i = 0; i < this.input.Length; i += Vector<uint>.Count)
{
Vector<uint> a = new Vector<uint>(this.input, i);
a = a / v;
a.CopyTo(this.result, i);
}
}
}
}

54
tests/ImageSharp.Benchmarks/General/Vectorization/MulFloat.cs

@ -0,0 +1,54 @@
namespace ImageSharp.Benchmarks.General.Vectorization
{
using System.Numerics;
using BenchmarkDotNet.Attributes;
public class MulFloat
{
private float[] input;
private float[] result;
[Params(32)]
public int InputSize { get; set; }
private float testValue;
[Setup]
public void Setup()
{
this.input = new float[this.InputSize];
this.result = new float[this.InputSize];
this.testValue = 42;
for (int i = 0; i < this.InputSize; i++)
{
this.input[i] = (uint)i;
}
}
[Benchmark(Baseline = true)]
public void Standard()
{
float v = this.testValue;
for (int i = 0; i < this.input.Length; i++)
{
this.result[i] = this.input[i] * v;
}
}
[Benchmark]
public void Simd()
{
Vector<float> v = new Vector<float>(this.testValue);
for (int i = 0; i < this.input.Length; i += Vector<uint>.Count)
{
Vector<float> a = new Vector<float>(this.input, i);
a = a * v;
a.CopyTo(this.result, i);
}
}
}
}

54
tests/ImageSharp.Benchmarks/General/Vectorization/MulUInt32.cs

@ -0,0 +1,54 @@
namespace ImageSharp.Benchmarks.General.Vectorization
{
using System.Numerics;
using BenchmarkDotNet.Attributes;
public class MulUInt32
{
private uint[] input;
private uint[] result;
[Params(32)]
public int InputSize { get; set; }
private uint testValue;
[Setup]
public void Setup()
{
this.input = new uint[this.InputSize];
this.result = new uint[this.InputSize];
this.testValue = 42;
for (int i = 0; i < this.InputSize; i++)
{
this.input[i] = (uint)i;
}
}
[Benchmark(Baseline = true)]
public void Standard()
{
uint v = this.testValue;
for (int i = 0; i < this.input.Length; i++)
{
this.result[i] = this.input[i] * v;
}
}
[Benchmark]
public void Simd()
{
Vector<uint> v = new Vector<uint>(this.testValue);
for (int i = 0; i < this.input.Length; i += Vector<uint>.Count)
{
Vector<uint> a = new Vector<uint>(this.input, i);
a = a * v;
a.CopyTo(this.result, i);
}
}
}
}

62
tests/ImageSharp.Benchmarks/General/Vectorization/ReinterpretUInt32AsFloat.cs

@ -0,0 +1,62 @@
namespace ImageSharp.Benchmarks.General.Vectorization
{
using System.Numerics;
using System.Runtime.InteropServices;
using BenchmarkDotNet.Attributes;
public class ReinterpretUInt32AsFloat
{
private uint[] input;
private float[] result;
[Params(32)]
public int InputSize { get; set; }
[StructLayout(LayoutKind.Explicit)]
struct UIntFloatUnion
{
[FieldOffset(0)]
public float f;
[FieldOffset(0)]
public uint i;
}
[Setup]
public void Setup()
{
this.input = new uint[this.InputSize];
this.result = new float[this.InputSize];
for (int i = 0; i < this.InputSize; i++)
{
this.input[i] = (uint)i;
}
}
[Benchmark(Baseline = true)]
public void Standard()
{
UIntFloatUnion u = default(UIntFloatUnion);
for (int i = 0; i < this.input.Length; i++)
{
u.i = this.input[i];
this.result[i] = u.f;
}
}
[Benchmark]
public void Simd()
{
for (int i = 0; i < this.input.Length; i += Vector<uint>.Count)
{
Vector<uint> a = new Vector<uint>(this.input, i);
Vector<float> b = Vector.AsVectorSingle(a);
b.CopyTo(this.result, i);
}
}
}
}

17
tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj

@ -109,6 +109,10 @@
<HintPath>..\..\packages\System.Reflection.Metadata.1.3.0\lib\portable-net45+win8\System.Reflection.Metadata.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Runtime.CompilerServices.Unsafe, Version=4.0.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\..\packages\System.Runtime.CompilerServices.Unsafe.4.3.0\lib\netstandard1.0\System.Runtime.CompilerServices.Unsafe.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Security.Cryptography.Algorithms, Version=4.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\..\packages\System.Security.Cryptography.Algorithms.4.2.0\lib\net461\System.Security.Cryptography.Algorithms.dll</HintPath>
<Private>True</Private>
@ -202,6 +206,15 @@
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="..\ImageSharp.Benchmarks\Color\Bulk\PixelAccessorVirtualCopy.cs">
<Link>Benchmarks\PixelAccessorVirtualCopy.cs</Link>
</Compile>
<Compile Include="..\ImageSharp.Tests\Colors\BulkPixelOperationsTests.cs">
<Link>Tests\Colors\BulkPixelOperationsTests.cs</Link>
</Compile>
<Compile Include="..\ImageSharp.Tests\Common\PinnedBufferTests.cs">
<Link>Tests\Common\PinnedBufferTests.cs</Link>
</Compile>
<Compile Include="..\ImageSharp.Tests\Drawing\PolygonTests.cs">
<Link>Tests\Drawing\PolygonTests.cs</Link>
</Compile>
@ -327,9 +340,7 @@
<ItemGroup>
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
</ItemGroup>
<ItemGroup>
<Folder Include="Benchmarks\" />
</ItemGroup>
<ItemGroup />
<ItemGroup>
<Analyzer Include="..\..\packages\Microsoft.CodeAnalysis.Analyzers.1.1.0\analyzers\dotnet\cs\Microsoft.CodeAnalysis.Analyzers.dll" />
<Analyzer Include="..\..\packages\Microsoft.CodeAnalysis.Analyzers.1.1.0\analyzers\dotnet\cs\Microsoft.CodeAnalysis.CSharp.Analyzers.dll" />

16
tests/ImageSharp.Sandbox46/Program.cs

@ -8,6 +8,7 @@ namespace ImageSharp.Sandbox46
using System;
using System.Runtime.DesignerServices;
using ImageSharp.Benchmarks.Color.Bulk;
using ImageSharp.Tests;
using Xunit.Abstractions;
@ -36,7 +37,20 @@ namespace ImageSharp.Sandbox46
/// </param>
public static void Main(string[] args)
{
RunDecodeJpegProfilingTests();
// RunDecodeJpegProfilingTests();
TestPixelAccessorCopyFromXyzw();
Console.ReadLine();
}
private static void TestPixelAccessorCopyFromXyzw()
{
PixelAccessorVirtualCopy benchmark = new PixelAccessorVirtualCopy();
benchmark.Width = 64;
benchmark.Setup();
benchmark.CopyRawUnsafeInlined();
benchmark.Cleanup();
}
private static void RunDecodeJpegProfilingTests()

4
tests/ImageSharp.Sandbox46/app.config

@ -10,6 +10,10 @@
<assemblyIdentity name="System.Collections.Immutable" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-1.2.0.0" newVersion="1.2.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.2.0" newVersion="4.0.2.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>

1
tests/ImageSharp.Sandbox46/packages.config

@ -29,6 +29,7 @@
<package id="System.Reflection.Primitives" version="4.0.1" targetFramework="net461" />
<package id="System.Resources.ResourceManager" version="4.0.1" targetFramework="net461" />
<package id="System.Runtime" version="4.1.0" targetFramework="net461" />
<package id="System.Runtime.CompilerServices.Unsafe" version="4.3.0" targetFramework="net461" />
<package id="System.Runtime.Extensions" version="4.1.0" targetFramework="net461" />
<package id="System.Runtime.Handles" version="4.0.1" targetFramework="net461" />
<package id="System.Runtime.InteropServices" version="4.1.0" targetFramework="net461" />

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

@ -0,0 +1,340 @@
namespace ImageSharp.Tests.Colors
{
using System;
using System.Numerics;
using Xunit;
public class BulkPixelOperationsTests
{
public class Color : BulkPixelOperationsTests<ImageSharp.Color>
{
// For 4.6 test runner MemberData does not work without redeclaring the public field in the derived test class:
public static new TheoryData<int> ArraySizesData => new TheoryData<int> { 7, 16, 1111 };
}
public class Argb : BulkPixelOperationsTests<ImageSharp.Argb>
{
// For 4.6 test runner MemberData does not work without redeclaring the public field in the derived test class:
public static new TheoryData<int> ArraySizesData => new TheoryData<int> { 7, 16, 1111 };
}
[Theory]
[WithBlankImages(1, 1, PixelTypes.All)]
public void GetGlobalInstance<TColor>(TestImageProvider<TColor> dummy)
where TColor:struct, IPixel<TColor>
{
Assert.NotNull(BulkPixelOperations<TColor>.Instance);
}
}
public abstract class BulkPixelOperationsTests<TColor>
where TColor : struct, IPixel<TColor>
{
public static TheoryData<int> ArraySizesData => new TheoryData<int> { 7, 16, 1111 };
[Theory]
[MemberData(nameof(ArraySizesData))]
public void PackFromVector4(int count)
{
Vector4[] source = CreateVector4TestData(count);
TColor[] expected = new TColor[count];
for (int i = 0; i < count; i++)
{
expected[i].PackFromVector4(source[i]);
}
TestOperation(
source,
expected,
(ops, s, d) => ops.PackFromVector4(s, d, count)
);
}
[Theory]
[MemberData(nameof(ArraySizesData))]
public void PackToVector4(int count)
{
TColor[] source = CreatePixelTestData(count);
Vector4[] expected = new Vector4[count];
for (int i = 0; i < count; i++)
{
expected[i] = source[i].ToVector4();
}
TestOperation(
source,
expected,
(ops, s, d) => ops.ToVector4(s, d, count)
);
}
[Theory]
[MemberData(nameof(ArraySizesData))]
public void PackFromXyzBytes(int count)
{
byte[] source = CreateByteTestData(count * 3);
TColor[] expected = new TColor[count];
for (int i = 0; i < count; i++)
{
int i3 = i * 3;
expected[i].PackFromBytes(source[i3 + 0], source[i3 + 1], source[i3 + 2], 255);
}
TestOperation(
source,
expected,
(ops, s, d) => ops.PackFromXyzBytes(s, d, count)
);
}
[Theory]
[MemberData(nameof(ArraySizesData))]
public void PackToXyzBytes(int count)
{
TColor[] source = CreatePixelTestData(count);
byte[] expected = new byte[count * 3];
for (int i = 0; i < count; i++)
{
int i3 = i * 3;
source[i].ToXyzBytes(expected, i3);
}
TestOperation(
source,
expected,
(ops, s, d) => ops.ToXyzBytes(s, d, count)
);
}
[Theory]
[MemberData(nameof(ArraySizesData))]
public void PackFromXyzwBytes(int count)
{
byte[] source = CreateByteTestData(count * 4);
TColor[] expected = new TColor[count];
for (int i = 0; i < count; i++)
{
int i4 = i * 4;
expected[i].PackFromBytes(source[i4 + 0], source[i4 + 1], source[i4 + 2], source[i4 + 3]);
}
TestOperation(
source,
expected,
(ops, s, d) => ops.PackFromXyzwBytes(s, d, count)
);
}
[Theory]
[MemberData(nameof(ArraySizesData))]
public void PackToXyzwBytes(int count)
{
TColor[] source = CreatePixelTestData(count);
byte[] expected = new byte[count * 4];
for (int i = 0; i < count; i++)
{
int i4 = i * 4;
source[i].ToXyzwBytes(expected, i4);
}
TestOperation(
source,
expected,
(ops, s, d) => ops.ToXyzwBytes(s, d, count)
);
}
[Theory]
[MemberData(nameof(ArraySizesData))]
public void PackFromZyxBytes(int count)
{
byte[] source = CreateByteTestData(count * 3);
TColor[] expected = new TColor[count];
for (int i = 0; i < count; i++)
{
int i3 = i * 3;
expected[i].PackFromBytes(source[i3 + 2], source[i3 + 1], source[i3 + 0], 255);
}
TestOperation(
source,
expected,
(ops, s, d) => ops.PackFromZyxBytes(s, d, count)
);
}
[Theory]
[MemberData(nameof(ArraySizesData))]
public void PackToZyxBytes(int count)
{
TColor[] source = CreatePixelTestData(count);
byte[] expected = new byte[count * 3];
for (int i = 0; i < count; i++)
{
int i3 = i * 3;
source[i].ToZyxBytes(expected, i3);
}
TestOperation(
source,
expected,
(ops, s, d) => ops.ToZyxBytes(s, d, count)
);
}
[Theory]
[MemberData(nameof(ArraySizesData))]
public void PackFromZyxwBytes(int count)
{
byte[] source = CreateByteTestData(count * 4);
TColor[] expected = new TColor[count];
for (int i = 0; i < count; i++)
{
int i4 = i * 4;
expected[i].PackFromBytes(source[i4 + 2], source[i4 + 1], source[i4 + 0], source[i4 + 3]);
}
TestOperation(
source,
expected,
(ops, s, d) => ops.PackFromZyxwBytes(s, d, count)
);
}
[Theory]
[MemberData(nameof(ArraySizesData))]
public void PackToZyxwBytes(int count)
{
TColor[] source = CreatePixelTestData(count);
byte[] expected = new byte[count * 4];
for (int i = 0; i < count; i++)
{
int i4 = i * 4;
source[i].ToZyxwBytes(expected, i4);
}
TestOperation(
source,
expected,
(ops, s, d) => ops.ToZyxwBytes(s, d, count)
);
}
private class TestBuffers<TSource, TDest> : IDisposable
where TSource : struct
where TDest : struct
{
public PinnedBuffer<TSource> SourceBuffer { get; }
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 TestBuffers(TSource[] source, TDest[] expectedDest)
{
this.SourceBuffer = new PinnedBuffer<TSource>(source);
this.ExpectedDestBuffer = new PinnedBuffer<TDest>(expectedDest);
this.ActualDestBuffer = new PinnedBuffer<TDest>(expectedDest.Length);
}
public void Dispose()
{
this.SourceBuffer.Dispose();
this.ActualDestBuffer.Dispose();
this.ExpectedDestBuffer.Dispose();
}
public void Verify()
{
int count = this.ExpectedDestBuffer.Count;
TDest[] expected = this.ExpectedDestBuffer.Array;
TDest[] actual = this.ActualDestBuffer.Array;
for (int i = 0; i < count; i++)
{
Assert.Equal(expected[i], actual[i]);
}
}
}
private static void TestOperation<TSource, TDest>(
TSource[] source,
TDest[] expected,
Action<BulkPixelOperations<TColor>, BufferPointer<TSource>, BufferPointer<TDest>> action)
where TSource : struct
where TDest : struct
{
using (var buffers = new TestBuffers<TSource, TDest>(source, expected))
{
action(BulkPixelOperations<TColor>.Instance, buffers.Source, buffers.ActualDest);
buffers.Verify();
}
}
private static Vector4[] CreateVector4TestData(int length)
{
Vector4[] result = new Vector4[length];
Random rnd = new Random(42); // Deterministic random values
for (int i = 0; i < result.Length; i++)
{
result[i] = GetVector(rnd);
}
return result;
}
private static TColor[] CreatePixelTestData(int length)
{
TColor[] result = new TColor[length];
Random rnd = new Random(42); // Deterministic random values
for (int i = 0; i < result.Length; i++)
{
Vector4 v = GetVector(rnd);
result[i].PackFromVector4(v);
}
return result;
}
private static byte[] CreateByteTestData(int length)
{
byte[] result = new byte[length];
Random rnd = new Random(42); // Deterministic random values
for (int i = 0; i < result.Length; i++)
{
result[i] = (byte)rnd.Next(255);
}
return result;
}
private static Vector4 GetVector(Random rnd)
{
return new Vector4(
(float)rnd.NextDouble(),
(float)rnd.NextDouble(),
(float)rnd.NextDouble(),
(float)rnd.NextDouble()
);
}
}
}

83
tests/ImageSharp.Tests/Common/ArrayPointerTests.cs

@ -1,83 +0,0 @@
// ReSharper disable ObjectCreationAsStatement
// ReSharper disable InconsistentNaming
namespace ImageSharp.Tests.Common
{
using System;
using Xunit;
public unsafe class ArrayPointerTests
{
public struct Foo
{
#pragma warning disable CS0414
private int a;
private double b;
#pragma warning restore CS0414
internal static Foo[] CreateArray(int size)
{
Foo[] result = new Foo[size];
for (int i = 0; i < size; i++)
{
result[i] = new Foo() { a = i, b = i };
}
return result;
}
}
[Fact]
public void ConstructWithoutOffset()
{
Foo[] array = Foo.CreateArray(3);
fixed (Foo* p = array)
{
// Act:
ArrayPointer<Foo> ap = new ArrayPointer<Foo>(array, p);
// Assert:
Assert.Equal(array, ap.Array);
Assert.Equal((IntPtr)p, ap.PointerAtOffset);
}
}
[Fact]
public void ConstructWithOffset()
{
Foo[] array = Foo.CreateArray(3);
int offset = 2;
fixed (Foo* p = array)
{
// Act:
ArrayPointer<Foo> ap = new ArrayPointer<Foo>(array, p, offset);
// Assert:
Assert.Equal(array, ap.Array);
Assert.Equal(offset, ap.Offset);
Assert.Equal((IntPtr)(p+offset), ap.PointerAtOffset);
}
}
[Fact]
public void Slice()
{
Foo[] array = Foo.CreateArray(5);
int offset0 = 2;
int offset1 = 2;
int totalOffset = offset0 + offset1;
fixed (Foo* p = array)
{
ArrayPointer<Foo> ap = new ArrayPointer<Foo>(array, p, offset0);
// Act:
ap = ap.Slice(offset1);
// Assert:
Assert.Equal(array, ap.Array);
Assert.Equal(totalOffset, ap.Offset);
Assert.Equal((IntPtr)(p + totalOffset), ap.PointerAtOffset);
}
}
}
}

167
tests/ImageSharp.Tests/Common/BufferPointerTests.cs

@ -0,0 +1,167 @@
// ReSharper disable ObjectCreationAsStatement
// ReSharper disable InconsistentNaming
namespace ImageSharp.Tests.Common
{
using System;
using System.Runtime.CompilerServices;
using Xunit;
public unsafe class BufferPointerTests
{
public struct Foo
{
public int A;
public double B;
internal static Foo[] CreateArray(int size)
{
Foo[] result = new Foo[size];
for (int i = 0; i < size; i++)
{
result[i] = new Foo() { A = i, B = i };
}
return result;
}
}
[Fact]
public void ConstructWithoutOffset()
{
Foo[] array = Foo.CreateArray(3);
fixed (Foo* p = array)
{
// Act:
BufferPointer<Foo> ap = new BufferPointer<Foo>(array, p);
// Assert:
Assert.Equal(array, ap.Array);
Assert.Equal((IntPtr)p, ap.PointerAtOffset);
}
}
[Fact]
public void ConstructWithOffset()
{
Foo[] array = Foo.CreateArray(3);
int offset = 2;
fixed (Foo* p = array)
{
// Act:
BufferPointer<Foo> ap = new BufferPointer<Foo>(array, p, offset);
// Assert:
Assert.Equal(array, ap.Array);
Assert.Equal(offset, ap.Offset);
Assert.Equal((IntPtr)(p+offset), ap.PointerAtOffset);
}
}
[Fact]
public void Slice()
{
Foo[] array = Foo.CreateArray(5);
int offset0 = 2;
int offset1 = 2;
int totalOffset = offset0 + offset1;
fixed (Foo* p = array)
{
BufferPointer<Foo> ap = new BufferPointer<Foo>(array, p, offset0);
// Act:
ap = ap.Slice(offset1);
// Assert:
Assert.Equal(array, ap.Array);
Assert.Equal(totalOffset, ap.Offset);
Assert.Equal((IntPtr)(p + totalOffset), ap.PointerAtOffset);
}
}
public class Copy
{
[Theory]
[InlineData(4)]
[InlineData(1500)]
public void GenericToOwnType(int count)
{
Foo[] source = Foo.CreateArray(count + 2);
Foo[] dest = new Foo[count + 5];
fixed (Foo* pSource = source)
fixed (Foo* pDest = dest)
{
BufferPointer<Foo> apSource = new BufferPointer<Foo>(source, pSource);
BufferPointer<Foo> apDest = new BufferPointer<Foo>(dest, pDest);
BufferPointer.Copy(apSource, apDest, count);
}
Assert.Equal(source[0], dest[0]);
Assert.Equal(source[count-1], dest[count-1]);
Assert.NotEqual(source[count], dest[count]);
}
[Theory]
[InlineData(4)]
[InlineData(1500)]
public void GenericToBytes(int count)
{
int destCount = count * sizeof(Foo);
Foo[] source = Foo.CreateArray(count + 2);
byte[] dest = new byte[destCount + sizeof(Foo) + 1];
fixed (Foo* pSource = source)
fixed (byte* pDest = dest)
{
BufferPointer<Foo> apSource = new BufferPointer<Foo>(source, pSource);
BufferPointer<byte> apDest = new BufferPointer<byte>(dest, pDest);
BufferPointer.Copy(apSource, apDest, count);
}
Assert.True(ElementsAreEqual(source, dest, 0));
Assert.True(ElementsAreEqual(source, dest, count - 1));
Assert.False(ElementsAreEqual(source, dest, count));
}
[Theory]
[InlineData(4)]
[InlineData(1500)]
public void BytesToGeneric(int count)
{
int destCount = count * sizeof(Foo);
byte[] source = new byte[destCount + sizeof(Foo) + 1];
Foo[] dest = Foo.CreateArray(count + 2);
fixed(byte* pSource = source)
fixed (Foo* pDest = dest)
{
BufferPointer<byte> apSource = new BufferPointer<byte>(source, pSource);
BufferPointer<Foo> apDest = new BufferPointer<Foo>(dest, pDest);
BufferPointer.Copy(apSource, apDest, count);
}
Assert.True(ElementsAreEqual(dest, source, 0));
Assert.True(ElementsAreEqual(dest, source, count - 1));
Assert.False(ElementsAreEqual(dest, source, count));
}
private static bool ElementsAreEqual(Foo[] array, byte[] rawArray, int index)
{
fixed (Foo* pArray = array)
fixed (byte* pRaw = rawArray)
{
Foo* pCasted = (Foo*)pRaw;
Foo val1 = pArray[index];
Foo val2 = pCasted[index];
return val1.Equals(val2);
}
}
}
}
}

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

@ -0,0 +1,94 @@
namespace ImageSharp.Tests.Common
{
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Xunit;
public unsafe class PinnedBufferTests
{
public struct Foo
{
public int A;
public double B;
}
[Theory]
[InlineData(42)]
[InlineData(1111)]
public void ConstructWithOwnArray(int count)
{
using (PinnedBuffer<Foo> buffer = new PinnedBuffer<Foo>(count))
{
Assert.False(buffer.IsDisposedOrLostArrayOwnership);
Assert.NotNull(buffer.Array);
Assert.Equal(count, buffer.Count);
Assert.True(buffer.Array.Length >= count);
VerifyPointer(buffer);
}
}
[Theory]
[InlineData(42)]
[InlineData(1111)]
public void ConstructWithExistingArray(int count)
{
Foo[] array = new Foo[count];
using (PinnedBuffer<Foo> buffer = new PinnedBuffer<Foo>(array))
{
Assert.False(buffer.IsDisposedOrLostArrayOwnership);
Assert.Equal(array, buffer.Array);
Assert.Equal(count, buffer.Count);
VerifyPointer(buffer);
}
}
[Fact]
public void Dispose()
{
PinnedBuffer<Foo> buffer = new PinnedBuffer<Foo>(42);
buffer.Dispose();
Assert.True(buffer.IsDisposedOrLostArrayOwnership);
}
[Fact]
public void Slice()
{
Foo[] a = { new Foo() { A = 1, B = 2 }, new Foo() { A = 3, B = 4 } };
using (PinnedBuffer<Foo> buffer = new PinnedBuffer<Foo>(a))
{
var arrayPtr = buffer.Slice();
Assert.Equal(a, arrayPtr.Array);
Assert.Equal(0, arrayPtr.Offset);
Assert.Equal(buffer.Pointer, arrayPtr.PointerAtOffset);
}
}
[Fact]
public void UnPinAndTakeArrayOwnership()
{
Foo[] data = null;
using (PinnedBuffer<Foo> buffer = new PinnedBuffer<Foo>(42))
{
data = buffer.UnPinAndTakeArrayOwnership();
Assert.True(buffer.IsDisposedOrLostArrayOwnership);
}
Assert.NotNull(data);
Assert.True(data.Length >= 42);
}
private static void VerifyPointer(PinnedBuffer<Foo> buffer)
{
IntPtr ptr = (IntPtr)Unsafe.AsPointer(ref buffer.Array[0]);
Assert.Equal(ptr, buffer.Pointer);
}
}
}

93
tests/ImageSharp.Tests/Common/PixelDataPoolTests.cs

@ -0,0 +1,93 @@
// <copyright file="PixelDataPoolTests.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Tests
{
using System.Linq;
using Xunit;
/// <summary>
/// Tests the <see cref="PixelDataPool{T}"/> class.
/// </summary>
public class PixelDataPoolTests
{
[Fact]
public void PixelDataPoolRentsMinimumSize()
{
Color[] pixels = PixelDataPool<Color>.Rent(1024);
Assert.True(pixels.Length >= 1024);
}
[Fact]
public void PixelDataPoolRentsEmptyArray()
{
for (int i = 16; i < 1024; i += 16)
{
Color[] pixels = PixelDataPool<Color>.Rent(i);
Assert.True(pixels.All(p => p == default(Color)));
PixelDataPool<Color>.Return(pixels);
}
for (int i = 16; i < 1024; i += 16)
{
Color[] pixels = PixelDataPool<Color>.Rent(i);
Assert.True(pixels.All(p => p == default(Color)));
PixelDataPool<Color>.Return(pixels);
}
}
[Fact]
public void PixelDataPoolDoesNotThrowWhenReturningNonPooled()
{
Color[] pixels = new Color[1024];
PixelDataPool<Color>.Return(pixels);
Assert.True(pixels.Length >= 1024);
}
[Fact]
public void PixelDataPoolCleansRentedArray()
{
Color[] pixels = PixelDataPool<Color>.Rent(256);
for (int i = 0; i < pixels.Length; i++)
{
pixels[i] = Color.Azure;
}
Assert.True(pixels.All(p => p == Color.Azure));
PixelDataPool<Color>.Return(pixels);
Assert.True(pixels.All(p => p == default(Color)));
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public void CalculateMaxArrayLength(bool isRawData)
{
int max = isRawData ? PixelDataPool<int>.CalculateMaxArrayLength()
: PixelDataPool<Color>.CalculateMaxArrayLength();
Assert.Equal(max < int.MaxValue, !isRawData);
}
[Fact]
public void RentNonIPixelData()
{
byte[] data = PixelDataPool<byte>.Rent(16384);
Assert.True(data.Length >= 16384);
}
}
}

74
tests/ImageSharp.Tests/Image/PixelPoolTests.cs

@ -1,74 +0,0 @@
// <copyright file="PixelPoolTests.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Tests
{
using System.Linq;
using Xunit;
/// <summary>
/// Tests the <see cref="PixelAccessor"/> class.
/// </summary>
public class PixelPoolTests
{
[Fact]
public void PixelPoolRentsMinimumSize()
{
Color[] pixels = PixelPool<Color>.RentPixels(1024);
Assert.True(pixels.Length >= 1024);
}
[Fact]
public void PixelPoolRentsEmptyArray()
{
for (int i = 16; i < 1024; i += 16)
{
Color[] pixels = PixelPool<Color>.RentPixels(i);
Assert.True(pixels.All(p => p == default(Color)));
PixelPool<Color>.ReturnPixels(pixels);
}
for (int i = 16; i < 1024; i += 16)
{
Color[] pixels = PixelPool<Color>.RentPixels(i);
Assert.True(pixels.All(p => p == default(Color)));
PixelPool<Color>.ReturnPixels(pixels);
}
}
[Fact]
public void PixelPoolDoesNotThrowWhenReturningNonPooled()
{
Color[] pixels = new Color[1024];
PixelPool<Color>.ReturnPixels(pixels);
Assert.True(pixels.Length >= 1024);
}
[Fact]
public void PixelPoolCleansRentedArray()
{
Color[] pixels = PixelPool<Color>.RentPixels(256);
for (int i = 0; i < pixels.Length; i++)
{
pixels[i] = Color.Azure;
}
Assert.True(pixels.All(p => p == Color.Azure));
PixelPool<Color>.ReturnPixels(pixels);
Assert.True(pixels.All(p => p == default(Color)));
}
}
}
Loading…
Cancel
Save