Browse Source

PixelAccessor<T> and PixelArea<T> using PinnedBuffer<T>

af/merge-core
Anton Firszov 9 years ago
parent
commit
c9e17d8966
  1. 168
      src/ImageSharp/Colors/PackedPixel/BulkPixelOperations.cs
  2. 256
      src/ImageSharp/Colors/PackedPixel/BulkPixelOperations{TColor}.cs
  3. 32
      src/ImageSharp/Common/Memory/ArrayPointer{T}.cs
  4. 89
      src/ImageSharp/Common/Memory/PinnedBuffer.cs
  5. 60
      src/ImageSharp/Common/Memory/PixelDataPool{T}.cs
  6. 10
      src/ImageSharp/Image/ImageBase{TColor}.cs
  7. 139
      src/ImageSharp/Image/PixelAccessor{TColor}.cs
  8. 108
      src/ImageSharp/Image/PixelArea{TColor}.cs
  9. 43
      src/ImageSharp/Image/PixelPool{TColor}.cs
  10. 6
      tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj
  11. 25
      tests/ImageSharp.Tests/Common/PinnedBufferTests.cs
  12. 49
      tests/ImageSharp.Tests/Image/PixelPoolTests.cs

168
src/ImageSharp/Colors/PackedPixel/BulkPixelOperations.cs

@ -1,168 +0,0 @@
namespace ImageSharp
{
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
public unsafe class BulkPixelOperations<TColor>
where TColor : struct, IPixel<TColor>
{
public static BulkPixelOperations<TColor> Instance { get; } = default(TColor).BulkOperations;
private static readonly int ColorSize = Unsafe.SizeOf<TColor>();
internal virtual void PackFromVector4(
ArrayPointer<Vector4> sourceVectors,
ArrayPointer<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;
}
}
internal virtual void PackToVector4(
ArrayPointer<TColor> sourceColors,
ArrayPointer<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++;
}
}
internal virtual void PackFromXyzBytes(
ArrayPointer<byte> sourceBytes,
ArrayPointer<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;
}
}
internal virtual void PackToXyzBytes(
ArrayPointer<TColor> sourceColors,
ArrayPointer<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;
}
}
internal virtual void PackFromXyzwBytes(ArrayPointer<byte> sourceBytes, ArrayPointer<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;
}
}
internal virtual void PackToXyzwBytes(ArrayPointer<TColor> sourceColors, ArrayPointer<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;
}
}
internal virtual void PackFromZyxBytes(ArrayPointer<byte> sourceBytes, ArrayPointer<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;
}
}
internal virtual void PackToZyxBytes(ArrayPointer<TColor> sourceColors, ArrayPointer<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;
}
}
internal virtual void PackFromZyxwBytes(ArrayPointer<byte> sourceBytes, ArrayPointer<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;
}
}
internal virtual void PackToZyxwBytes(ArrayPointer<TColor> sourceColors, ArrayPointer<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;
}
}
}
}

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).BulkOperations;
/// <summary>
/// Bulk version of <see cref="IPixel.PackFromVector4(Vector4)"/>
/// </summary>
/// <param name="sourceVectors">The <see cref="ArrayPointer{Vector4}"/> to the source vectors.</param>
/// <param name="destColors">The <see cref="ArrayPointer{TColor}"/> to the destination colors.</param>
/// <param name="count">The number of pixels to convert.</param>
internal virtual void PackFromVector4(
ArrayPointer<Vector4> sourceVectors,
ArrayPointer<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="ArrayPointer{TColor}"/> to the source colors.</param>
/// <param name="destVectors">The <see cref="ArrayPointer{Vector4}"/> to the destination vectors.</param>
/// <param name="count">The number of pixels to convert.</param>
internal virtual void PackToVector4(
ArrayPointer<TColor> sourceColors,
ArrayPointer<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"></param>
/// <param name="destColors"></param>
/// <param name="count"></param>
internal virtual void PackFromXyzBytes(
ArrayPointer<byte> sourceBytes,
ArrayPointer<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"></param>
/// <param name="destBytes"></param>
/// <param name="count"></param>
internal virtual void PackToXyzBytes(ArrayPointer<TColor> sourceColors, ArrayPointer<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"></param>
/// <param name="destColors"></param>
/// <param name="count"></param>
internal virtual void PackFromXyzwBytes(
ArrayPointer<byte> sourceBytes,
ArrayPointer<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"></param>
/// <param name="destBytes"></param>
/// <param name="count"></param>
internal virtual void PackToXyzwBytes(
ArrayPointer<TColor> sourceColors,
ArrayPointer<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"></param>
/// <param name="destColors"></param>
/// <param name="count"></param>
internal virtual void PackFromZyxBytes(
ArrayPointer<byte> sourceBytes,
ArrayPointer<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"></param>
/// <param name="destBytes"></param>
/// <param name="count"></param>
internal virtual void PackToZyxBytes(ArrayPointer<TColor> sourceColors, ArrayPointer<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"></param>
/// <param name="destColors"></param>
/// <param name="count"></param>
internal virtual void PackFromZyxwBytes(
ArrayPointer<byte> sourceBytes,
ArrayPointer<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"></param>
/// <param name="destBytes"></param>
/// <param name="count"></param>
internal virtual void PackToZyxwBytes(
ArrayPointer<TColor> sourceColors,
ArrayPointer<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;
}
}
}
}

32
src/ImageSharp/Common/Memory/ArrayPointer{T}.cs

@ -68,27 +68,12 @@ namespace ImageSharp
/// </summary>
public IntPtr PointerAtOffset { get; private set; }
/// <summary>
/// Forms a slice out of the given ArrayPointer, beginning at 'offset'.
/// </summary>
/// <param name="offset">The offset in number of elements</param>
/// <returns>The offseted (sliced) ArrayPointer</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ArrayPointer<T> Slice(int offset)
{
ArrayPointer<T> result = default(ArrayPointer<T>);
result.Array = this.Array;
result.Offset = this.Offset + offset;
result.PointerAtOffset = this.PointerAtOffset + (Unsafe.SizeOf<T>() * offset);
return result;
}
/// <summary>
/// Convertes <see cref="ArrayPointer{T}"/> instance to a raw 'void*' pointer
/// </summary>
/// <param name="arrayPointer">The <see cref="ArrayPointer{T}"/> to convert</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static explicit operator void*(ArrayPointer<T> arrayPointer)
public static explicit operator void* (ArrayPointer<T> arrayPointer)
{
return (void*)arrayPointer.PointerAtOffset;
}
@ -102,5 +87,20 @@ namespace ImageSharp
{
return (byte*)arrayPointer.PointerAtOffset;
}
/// <summary>
/// Forms a slice out of the given ArrayPointer, beginning at 'offset'.
/// </summary>
/// <param name="offset">The offset in number of elements</param>
/// <returns>The offseted (sliced) ArrayPointer</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ArrayPointer<T> Slice(int offset)
{
ArrayPointer<T> result = default(ArrayPointer<T>);
result.Array = this.Array;
result.Offset = this.Offset + offset;
result.PointerAtOffset = this.PointerAtOffset + (Unsafe.SizeOf<T>() * offset);
return result;
}
}
}

89
src/ImageSharp/Common/Memory/PinnedBuffer.cs

@ -5,25 +5,23 @@ namespace ImageSharp
using System.Runtime.InteropServices;
/// <summary>
/// Manages a pinned buffer of 'T' as a Disposable resource.
/// Manages a pinned buffer of value type data 'T' as a Disposable resource.
/// The backing array is either pooled or comes from the outside.
/// TODO: Should replace the pinning/dispose logic in several classes like <see cref="PixelAccessor{TColor}"/> or <see cref="PixelArea{TColor}"/>!
/// </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;
private bool isBufferRented;
private bool isDisposed;
/// <summary>
/// TODO: Consider reusing functionality of <see cref="PixelPool{TColor}"/>
/// A value indicating wether this <see cref="PinnedBuffer{T}"/> instance should return the array to the pool.
/// </summary>
private static readonly ArrayPool<T> ArrayPool = ArrayPool<T>.Create();
private bool isPoolingOwner;
/// <summary>
/// Initializes a new instance of the <see cref="PinnedBuffer{T}"/> class.
/// </summary>
@ -31,8 +29,8 @@ namespace ImageSharp
public PinnedBuffer(int count)
{
this.Count = count;
this.Array = ArrayPool.Rent(count);
this.isBufferRented = true;
this.Array = PixelDataPool<T>.Rent(count);
this.isPoolingOwner = true;
this.Pin();
}
@ -48,7 +46,34 @@ namespace ImageSharp
}
/// <summary>
/// The count of "relevant" elements. Usually be smaller than 'Array.Length' when <see cref="Array"/> is pooled.
/// 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();
}
~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; }
@ -58,7 +83,7 @@ namespace ImageSharp
public T[] Array { get; private set; }
/// <summary>
/// Pointer to the pinned <see cref="Array"/>.
/// Gets a pointer to the pinned <see cref="Array"/>.
/// </summary>
public IntPtr Pointer { get; private set; }
@ -67,16 +92,16 @@ namespace ImageSharp
/// </summary>
public void Dispose()
{
if (this.isDisposed)
if (this.IsDisposedOrLostArrayOwnership)
{
return;
}
this.isDisposed = true;
this.IsDisposedOrLostArrayOwnership = true;
this.UnPin();
if (this.isBufferRented)
if (this.isPoolingOwner)
{
ArrayPool.Return(this.Array, true);
PixelDataPool<T>.Return(this.Array);
}
this.Array = null;
@ -85,12 +110,37 @@ namespace ImageSharp
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)
@ -100,10 +150,5 @@ namespace ImageSharp
this.handle.Free();
this.Pointer = IntPtr.Zero;
}
~PinnedBuffer()
{
this.UnPin();
}
}
}

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

@ -0,0 +1,60 @@
// <copyright file="PixelDataPool{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 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()
{
if (typeof(IPixel).IsAssignableFrom(typeof(T)))
{
const int MaximumExpectedImageSize = 16384;
return MaximumExpectedImageSize * MaximumExpectedImageSize;
}
else
{
return int.MaxValue;
}
}
}
}

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

@ -162,13 +162,15 @@ 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");
// TODO: This check was useful. We can introduce a bool PixelAccessor<TColor>.IsBoundToImage to re-introduce it.
// 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 +224,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 +232,7 @@ namespace ImageSharp
/// </summary>
private void ReturnPixels()
{
PixelPool<TColor>.ReturnPixels(this.pixelBuffer);
PixelDataPool<TColor>.Return(this.pixelBuffer);
this.pixelBuffer = null;
}
}

139
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,7 +49,7 @@ 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;
}
@ -70,7 +60,7 @@ namespace ImageSharp
/// <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)
: this(width, height, new PinnedBuffer<TColor>(width * height, pixels))
{
}
@ -80,7 +70,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, PixelPool<TColor>.RentPixels(width * height), true)
: this(width, height, new PinnedBuffer<TColor>(width * height))
{
}
@ -90,19 +80,18 @@ 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}.");
}
//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;
}
@ -114,21 +103,16 @@ namespace ImageSharp
{
this.Dispose();
}
/// <summary>
/// Gets a value indicating whether the current pixel buffer is from a pooled source.
/// </summary>
public bool PooledMemory { get; private set; }
/// <summary>
/// Gets the pixel buffer array.
/// </summary>
public TColor[] PixelBuffer => this.pixelBuffer;
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.
@ -246,24 +230,18 @@ 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>
@ -280,13 +258,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;
}
@ -514,53 +491,57 @@ 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>
///// 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.

108
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,11 @@ 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 +119,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();
this.byteBuffer = new PinnedBuffer<byte>(this.Length);
this.PixelBase = (byte*)this.byteBuffer.Pointer;
}
/// <summary>
/// Finalizes an instance of the <see cref="PixelArea{TColor}"/> class.
/// </summary>
~PixelArea()
{
this.Dispose(false);
}
/// <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 +142,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.
@ -188,26 +163,19 @@ namespace ImageSharp
/// Gets the width.
/// </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>
@ -281,38 +249,6 @@ namespace ImageSharp
nameof(bytes),
$"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;
}
}
}
}

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

@ -1,43 +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;
// TODO: Consider refactoring this into a more general ClearPool<T>, so we can use it in PinnedBuffer<T>!
/// <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);
}
}
}

6
tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj

@ -212,6 +212,9 @@
<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>
@ -248,6 +251,9 @@
<Compile Include="..\ImageSharp.Tests\Formats\Jpg\YCbCrImageTests.cs">
<Link>Tests\Formats\Jpg\YCbCrImageTests.cs</Link>
</Compile>
<Compile Include="..\ImageSharp.Tests\Image\PixelPoolTests.cs">
<Link>Tests\Image\PixelPoolTests.cs</Link>
</Compile>
<Compile Include="..\ImageSharp.Tests\MetaData\ImagePropertyTests.cs">
<Link>Tests\MetaData\ImagePropertyTests.cs</Link>
</Compile>

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

@ -22,6 +22,7 @@
{
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);
@ -38,6 +39,7 @@
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);
@ -45,6 +47,15 @@
}
}
[Fact]
public void Dispose()
{
PinnedBuffer<Foo> buffer = new PinnedBuffer<Foo>(42);
buffer.Dispose();
Assert.True(buffer.IsDisposedOrLostArrayOwnership);
}
[Fact]
public void GetArrayPointer()
{
@ -60,6 +71,20 @@
}
}
[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]);

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

@ -1,4 +1,4 @@
// <copyright file="PixelPoolTests.cs" company="James Jackson-South">
// <copyright file="PixelDataPoolTests.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
@ -10,54 +10,54 @@ namespace ImageSharp.Tests
using Xunit;
/// <summary>
/// Tests the <see cref="PixelAccessor"/> class.
/// Tests the <see cref="PixelDataPool{T}"/> class.
/// </summary>
public class PixelPoolTests
public class PixelDataPoolTests
{
[Fact]
public void PixelPoolRentsMinimumSize()
public void PixelDataPoolRentsMinimumSize()
{
Color[] pixels = PixelPool<Color>.RentPixels(1024);
Color[] pixels = PixelDataPool<Color>.Rent(1024);
Assert.True(pixels.Length >= 1024);
}
[Fact]
public void PixelPoolRentsEmptyArray()
public void PixelDataPoolRentsEmptyArray()
{
for (int i = 16; i < 1024; i += 16)
{
Color[] pixels = PixelPool<Color>.RentPixels(i);
Color[] pixels = PixelDataPool<Color>.Rent(i);
Assert.True(pixels.All(p => p == default(Color)));
PixelPool<Color>.ReturnPixels(pixels);
PixelDataPool<Color>.Return(pixels);
}
for (int i = 16; i < 1024; i += 16)
{
Color[] pixels = PixelPool<Color>.RentPixels(i);
Color[] pixels = PixelDataPool<Color>.Rent(i);
Assert.True(pixels.All(p => p == default(Color)));
PixelPool<Color>.ReturnPixels(pixels);
PixelDataPool<Color>.Return(pixels);
}
}
[Fact]
public void PixelPoolDoesNotThrowWhenReturningNonPooled()
public void PixelDataPoolDoesNotThrowWhenReturningNonPooled()
{
Color[] pixels = new Color[1024];
PixelPool<Color>.ReturnPixels(pixels);
PixelDataPool<Color>.Return(pixels);
Assert.True(pixels.Length >= 1024);
}
[Fact]
public void PixelPoolCleansRentedArray()
public void PixelDataPoolCleansRentedArray()
{
Color[] pixels = PixelPool<Color>.RentPixels(256);
Color[] pixels = PixelDataPool<Color>.Rent(256);
for (int i = 0; i < pixels.Length; i++)
{
@ -66,9 +66,28 @@ namespace ImageSharp.Tests
Assert.True(pixels.All(p => p == Color.Azure));
PixelPool<Color>.ReturnPixels(pixels);
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);
}
}
}
Loading…
Cancel
Save