Browse Source

Merge pull request #174 from JimBobSquarePants/drop-pointers

Drop all pointers
pull/180/head
James Jackson-South 9 years ago
committed by GitHub
parent
commit
98b397a897
  1. 2
      src/ImageSharp.Drawing/Brushes/ImageBrush{TColor}.cs
  2. 2
      src/ImageSharp.Drawing/Brushes/PatternBrush{TColor}.cs
  3. 2
      src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs
  4. 2
      src/ImageSharp.Drawing/Brushes/RecolorBrush{TColor}.cs
  5. 2
      src/ImageSharp.Drawing/Brushes/SolidBrush{TColor}.cs
  6. 186
      src/ImageSharp/Colors/Color.BulkOperations.cs
  7. 5
      src/ImageSharp/Colors/Color.cs
  8. 132
      src/ImageSharp/Colors/PackedPixel/BulkPixelOperations{TColor}.cs
  9. 99
      src/ImageSharp/Common/Memory/Buffer.cs
  10. 22
      src/ImageSharp/Common/Memory/Buffer2D.cs
  11. 10
      src/ImageSharp/Common/Memory/Buffer2DExtensions.cs
  12. 96
      src/ImageSharp/Common/Memory/BufferSpan.cs
  13. 88
      src/ImageSharp/Common/Memory/BufferSpan{T}.cs
  14. 4
      src/ImageSharp/Common/Memory/IBuffer2D.cs
  15. 11
      src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
  16. 45
      src/ImageSharp/Formats/Jpeg/Utils/JpegUtils.cs
  17. 41
      src/ImageSharp/Image/PixelAccessor{TColor}.cs
  18. 22
      src/ImageSharp/Image/PixelArea{TColor}.cs
  19. 34
      src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.Weights.cs
  20. 11
      src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.cs
  21. 4
      src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs
  22. 14
      tests/ImageSharp.Benchmarks/Color/Bulk/PackFromVector4ReferenceVsPointer.cs
  23. 8
      tests/ImageSharp.Benchmarks/Color/Bulk/PackFromXyzw.cs
  24. 8
      tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4.cs
  25. 8
      tests/ImageSharp.Benchmarks/Color/Bulk/ToXyz.cs
  26. 8
      tests/ImageSharp.Benchmarks/Color/Bulk/ToXyzw.cs
  27. 37
      tests/ImageSharp.Benchmarks/General/ArrayCopy.cs
  28. 6
      tests/ImageSharp.Benchmarks/General/ClearBuffer.cs
  29. 7
      tests/ImageSharp.Benchmarks/General/IterateArray.cs
  30. 360
      tests/ImageSharp.Benchmarks/General/PixelIndexing.cs
  31. 20
      tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs
  32. 32
      tests/ImageSharp.Tests/Common/Buffer2DTests.cs
  33. 275
      tests/ImageSharp.Tests/Common/BufferSpanTests.cs
  34. 100
      tests/ImageSharp.Tests/Common/BufferTests.cs
  35. 2
      tests/ImageSharp.Tests/Common/TestStructs.cs

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

@ -117,7 +117,7 @@ namespace ImageSharp.Drawing.Brushes
{ {
Guard.MustBeGreaterThanOrEqualTo(scanlineBuffer.Length, offset + scanlineWidth, nameof(scanlineWidth)); Guard.MustBeGreaterThanOrEqualTo(scanlineBuffer.Length, offset + scanlineWidth, nameof(scanlineWidth));
using (PinnedBuffer<float> buffer = new PinnedBuffer<float>(scanlineBuffer)) using (Buffer<float> buffer = new Buffer<float>(scanlineBuffer))
{ {
BufferSpan<float> slice = buffer.Slice(offset); BufferSpan<float> slice = buffer.Slice(offset);

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

@ -150,7 +150,7 @@ namespace ImageSharp.Drawing.Brushes
{ {
Guard.MustBeGreaterThanOrEqualTo(scanlineBuffer.Length, offset + scanlineWidth, nameof(scanlineWidth)); Guard.MustBeGreaterThanOrEqualTo(scanlineBuffer.Length, offset + scanlineWidth, nameof(scanlineWidth));
using (PinnedBuffer<float> buffer = new PinnedBuffer<float>(scanlineBuffer)) using (Buffer<float> buffer = new Buffer<float>(scanlineBuffer))
{ {
BufferSpan<float> slice = buffer.Slice(offset); BufferSpan<float> slice = buffer.Slice(offset);

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

@ -55,7 +55,7 @@ namespace ImageSharp.Drawing.Processors
{ {
DebugGuard.MustBeGreaterThanOrEqualTo(scanlineBuffer.Length, offset + scanlineWidth, nameof(scanlineWidth)); DebugGuard.MustBeGreaterThanOrEqualTo(scanlineBuffer.Length, offset + scanlineWidth, nameof(scanlineWidth));
using (PinnedBuffer<float> buffer = new PinnedBuffer<float>(scanlineBuffer)) using (Buffer<float> buffer = new Buffer<float>(scanlineBuffer))
{ {
BufferSpan<float> slice = buffer.Slice(offset); BufferSpan<float> slice = buffer.Slice(offset);

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

@ -141,7 +141,7 @@ namespace ImageSharp.Drawing.Brushes
{ {
Guard.MustBeGreaterThanOrEqualTo(scanlineBuffer.Length, offset + scanlineWidth, nameof(scanlineWidth)); Guard.MustBeGreaterThanOrEqualTo(scanlineBuffer.Length, offset + scanlineWidth, nameof(scanlineWidth));
using (PinnedBuffer<float> buffer = new PinnedBuffer<float>(scanlineBuffer)) using (Buffer<float> buffer = new Buffer<float>(scanlineBuffer))
{ {
BufferSpan<float> slice = buffer.Slice(offset); BufferSpan<float> slice = buffer.Slice(offset);

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

@ -89,7 +89,7 @@ namespace ImageSharp.Drawing.Brushes
{ {
Guard.MustBeGreaterThanOrEqualTo(scanlineBuffer.Length, offset + scanlineWidth, nameof(scanlineWidth)); Guard.MustBeGreaterThanOrEqualTo(scanlineBuffer.Length, offset + scanlineWidth, nameof(scanlineWidth));
using (PinnedBuffer<float> buffer = new PinnedBuffer<float>(scanlineBuffer)) using (Buffer<float> buffer = new Buffer<float>(scanlineBuffer))
{ {
BufferSpan<float> slice = buffer.Slice(offset); BufferSpan<float> slice = buffer.Slice(offset);

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

@ -62,21 +62,23 @@ namespace ImageSharp
int unpackedRawCount = count * 4; int unpackedRawCount = count * 4;
uint* src = (uint*)sourceColors.PointerAtOffset; ref uint src = ref Unsafe.As<Color, uint>(ref sourceColors.DangerousGetPinnableReference());
uint* srcEnd = src + count;
using (PinnedBuffer<uint> tempBuf = new PinnedBuffer<uint>( using (Buffer<uint> tempBuf = new Buffer<uint>(
unpackedRawCount + Vector<uint>.Count)) unpackedRawCount + Vector<uint>.Count))
{ {
uint* tPtr = (uint*)tempBuf.Pointer;
uint[] temp = tempBuf.Array; uint[] temp = tempBuf.Array;
float[] fTemp = Unsafe.As<float[]>(temp); float[] fTemp = Unsafe.As<float[]>(temp);
UnpackedRGBA* dst = (UnpackedRGBA*)tPtr;
for (; src < srcEnd; src++, dst++) ref UnpackedRGBA tempBase = ref Unsafe.As<uint, UnpackedRGBA>(ref tempBuf[0]);
for (int i = 0; i < count; i++)
{ {
uint sVal = Unsafe.Add(ref src, i);
ref UnpackedRGBA dst = ref Unsafe.Add(ref tempBase, i);
// This call is the bottleneck now: // This call is the bottleneck now:
dst->Load(*src); dst.Load(sVal);
} }
for (int i = 0; i < unpackedRawCount; i += vecSize) for (int i = 0; i < unpackedRawCount; i += vecSize)
@ -91,7 +93,7 @@ namespace ImageSharp
vf.CopyTo(fTemp, i); vf.CopyTo(fTemp, i);
} }
BufferSpan.Copy<uint>(tempBuf, (BufferSpan<byte>)destVectors, unpackedRawCount); BufferSpan.Copy(tempBuf.Span.AsBytes(), destVectors.AsBytes(), unpackedRawCount * sizeof(uint));
} }
} }
@ -123,131 +125,181 @@ namespace ImageSharp
} }
/// <inheritdoc /> /// <inheritdoc />
internal override unsafe void PackFromXyzBytes(BufferSpan<byte> sourceBytes, BufferSpan<Color> destColors, int count) internal override void PackFromXyzBytes(
BufferSpan<byte> sourceBytes,
BufferSpan<Color> destColors,
int count)
{ {
byte* source = (byte*)sourceBytes; ref RGB24 sourceRef = ref Unsafe.As<byte, RGB24>(ref sourceBytes.DangerousGetPinnableReference());
byte* destination = (byte*)destColors; ref Color destRef = ref destColors.DangerousGetPinnableReference();
for (int x = 0; x < count; x++) for (int i = 0; i < count; i++)
{ {
Unsafe.Write(destination, (uint)(*source << 0 | *(source + 1) << 8 | *(source + 2) << 16 | 255 << 24)); ref RGB24 sp = ref Unsafe.Add(ref sourceRef, i);
ref Color dp = ref Unsafe.Add(ref destRef, i);
source += 3; Unsafe.As<Color, RGB24>(ref dp) = sp;
destination += 4; dp.A = 255;
} }
} }
/// <inheritdoc /> /// <inheritdoc />
internal override unsafe void ToXyzBytes(BufferSpan<Color> sourceColors, BufferSpan<byte> destBytes, int count) internal override void ToXyzBytes(BufferSpan<Color> sourceColors, BufferSpan<byte> destBytes, int count)
{ {
byte* source = (byte*)sourceColors; ref Color sourceRef = ref sourceColors.DangerousGetPinnableReference();
byte* destination = (byte*)destBytes; ref RGB24 destRef = ref Unsafe.As<byte, RGB24>(ref destBytes.DangerousGetPinnableReference());
for (int x = 0; x < count; x++) for (int i = 0; i < count; i++)
{ {
*destination = *(source + 0); ref Color sp = ref Unsafe.Add(ref sourceRef, i);
*(destination + 1) = *(source + 1); ref RGB24 dp = ref Unsafe.Add(ref destRef, i);
*(destination + 2) = *(source + 2);
source += 4; dp = Unsafe.As<Color, RGB24>(ref sp);
destination += 3;
} }
} }
/// <inheritdoc /> /// <inheritdoc />
internal override void PackFromXyzwBytes(BufferSpan<byte> sourceBytes, BufferSpan<Color> destColors, int count) internal override unsafe void PackFromXyzwBytes(
BufferSpan<byte> sourceBytes,
BufferSpan<Color> destColors,
int count)
{ {
BufferSpan.Copy(sourceBytes, destColors, count); BufferSpan.Copy(sourceBytes, destColors.AsBytes(), count * sizeof(Color));
} }
/// <inheritdoc /> /// <inheritdoc />
internal override void ToXyzwBytes(BufferSpan<Color> sourceColors, BufferSpan<byte> destBytes, int count) internal override unsafe void ToXyzwBytes(BufferSpan<Color> sourceColors, BufferSpan<byte> destBytes, int count)
{ {
BufferSpan.Copy(sourceColors, destBytes, count); BufferSpan.Copy(sourceColors.AsBytes(), destBytes, count * sizeof(Color));
} }
/// <inheritdoc /> /// <inheritdoc />
internal override unsafe void PackFromZyxBytes(BufferSpan<byte> sourceBytes, BufferSpan<Color> destColors, int count) internal override void PackFromZyxBytes(
BufferSpan<byte> sourceBytes,
BufferSpan<Color> destColors,
int count)
{ {
byte* source = (byte*)sourceBytes; ref RGB24 sourceRef = ref Unsafe.As<byte, RGB24>(ref sourceBytes.DangerousGetPinnableReference());
byte* destination = (byte*)destColors; ref Color destRef = ref destColors.DangerousGetPinnableReference();
for (int x = 0; x < count; x++) for (int i = 0; i < count; i++)
{ {
Unsafe.Write(destination, (uint)(*(source + 2) << 0 | *(source + 1) << 8 | *source << 16 | 255 << 24)); ref RGB24 sp = ref Unsafe.Add(ref sourceRef, i);
ref Color dp = ref Unsafe.Add(ref destRef, i);
source += 3; Unsafe.As<Color, RGB24>(ref dp) = sp.ToZyx();
destination += 4; dp.A = 255;
} }
} }
/// <inheritdoc /> /// <inheritdoc />
internal override unsafe void ToZyxBytes(BufferSpan<Color> sourceColors, BufferSpan<byte> destBytes, int count) internal override void ToZyxBytes(
BufferSpan<Color> sourceColors,
BufferSpan<byte> destBytes,
int count)
{ {
byte* source = (byte*)sourceColors; ref Color sourceRef = ref sourceColors.DangerousGetPinnableReference();
byte* destination = (byte*)destBytes; ref RGB24 destRef = ref Unsafe.As<byte, RGB24>(ref destBytes.DangerousGetPinnableReference());
for (int x = 0; x < count; x++) for (int i = 0; i < count; i++)
{ {
*destination = *(source + 2); ref Color sp = ref Unsafe.Add(ref sourceRef, i);
*(destination + 1) = *(source + 1); ref RGB24 dp = ref Unsafe.Add(ref destRef, i);
*(destination + 2) = *(source + 0);
source += 4; dp = Unsafe.As<Color, RGB24>(ref sp).ToZyx();
destination += 3;
} }
} }
/// <inheritdoc /> /// <inheritdoc />
internal override unsafe void PackFromZyxwBytes(BufferSpan<byte> sourceBytes, BufferSpan<Color> destColors, int count) internal override void PackFromZyxwBytes(
BufferSpan<byte> sourceBytes,
BufferSpan<Color> destColors,
int count)
{ {
byte* source = (byte*)sourceBytes; ref RGBA32 sourceRef = ref Unsafe.As<byte, RGBA32>(ref sourceBytes.DangerousGetPinnableReference());
byte* destination = (byte*)destColors; ref Color destRef = ref destColors.DangerousGetPinnableReference();
for (int x = 0; x < count; x++) for (int i = 0; i < count; i++)
{ {
Unsafe.Write(destination, (uint)(*(source + 2) << 0 | *(source + 1) << 8 | *source << 16 | *(source + 3) << 24)); ref RGBA32 sp = ref Unsafe.Add(ref sourceRef, i);
ref Color dp = ref Unsafe.Add(ref destRef, i);
source += 4; RGBA32 zyxw = sp.ToZyxw();
destination += 4; dp = Unsafe.As<RGBA32, Color>(ref zyxw);
} }
} }
/// <inheritdoc /> /// <inheritdoc />
internal override unsafe void ToZyxwBytes(BufferSpan<Color> sourceColors, BufferSpan<byte> destBytes, int count) internal override void ToZyxwBytes(
BufferSpan<Color> sourceColors,
BufferSpan<byte> destBytes,
int count)
{ {
byte* source = (byte*)sourceColors; ref Color sourceRef = ref sourceColors.DangerousGetPinnableReference();
byte* destination = (byte*)destBytes; ref RGBA32 destRef = ref Unsafe.As<byte, RGBA32>(ref destBytes.DangerousGetPinnableReference());
for (int x = 0; x < count; x++) for (int i = 0; i < count; i++)
{ {
*destination = *(source + 2); ref RGBA32 sp = ref Unsafe.As<Color, RGBA32>(ref Unsafe.Add(ref sourceRef, i));
*(destination + 1) = *(source + 1); ref RGBA32 dp = ref Unsafe.Add(ref destRef, i);
*(destination + 2) = *(source + 0); dp = sp.ToZyxw();
*(destination + 3) = *(source + 3);
source += 4;
destination += 4;
} }
} }
/// <summary>
/// Helper struct to manipulate 3-byte RGB data.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
private struct RGB24
{
private byte x;
private byte y;
private byte z;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public RGB24 ToZyx() => new RGB24 { x = this.z, y = this.y, z = this.x };
}
/// <summary>
/// Helper struct to manipulate 4-byte RGBA data.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
private struct RGBA32
{
private byte x;
private byte y;
private byte z;
private byte w;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public RGBA32 ToZyxw() => new RGBA32 { x = this.z, y = this.y, z = this.x, w = this.w };
}
/// <summary> /// <summary>
/// Value type to store <see cref="Color"/>-s unpacked into multiple <see cref="uint"/>-s. /// Value type to store <see cref="Color"/>-s unpacked into multiple <see cref="uint"/>-s.
/// </summary> /// </summary>
[StructLayout(LayoutKind.Sequential)]
private struct UnpackedRGBA private struct UnpackedRGBA
{ {
private uint r; private uint r;
private uint g; private uint g;
private uint b; private uint b;
private uint a; private uint a;
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Load(uint p) public void Load(uint p)
{ {
this.r = p; this.r = p;
this.g = p >> Color.GreenShift; this.g = p >> GreenShift;
this.b = p >> Color.BlueShift; this.b = p >> BlueShift;
this.a = p >> Color.AlphaShift; this.a = p >> AlphaShift;
} }
} }
} }

5
src/ImageSharp/Colors/Color.cs

@ -62,6 +62,7 @@ namespace ImageSharp
/// <param name="g">The green component.</param> /// <param name="g">The green component.</param>
/// <param name="b">The blue component.</param> /// <param name="b">The blue component.</param>
/// <param name="a">The alpha component.</param> /// <param name="a">The alpha component.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Color(byte r, byte g, byte b, byte a = 255) public Color(byte r, byte g, byte b, byte a = 255)
{ {
this.packedValue = Pack(r, g, b, a); this.packedValue = Pack(r, g, b, a);
@ -74,6 +75,7 @@ namespace ImageSharp
/// <param name="g">The green component.</param> /// <param name="g">The green component.</param>
/// <param name="b">The blue component.</param> /// <param name="b">The blue component.</param>
/// <param name="a">The alpha component.</param> /// <param name="a">The alpha component.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Color(float r, float g, float b, float a = 1) public Color(float r, float g, float b, float a = 1)
{ {
this.packedValue = Pack(r, g, b, a); this.packedValue = Pack(r, g, b, a);
@ -85,6 +87,7 @@ namespace ImageSharp
/// <param name="vector"> /// <param name="vector">
/// The vector containing the components for the packed vector. /// The vector containing the components for the packed vector.
/// </param> /// </param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Color(Vector3 vector) public Color(Vector3 vector)
{ {
this.packedValue = Pack(ref vector); this.packedValue = Pack(ref vector);
@ -96,6 +99,7 @@ namespace ImageSharp
/// <param name="vector"> /// <param name="vector">
/// The vector containing the components for the packed vector. /// The vector containing the components for the packed vector.
/// </param> /// </param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Color(Vector4 vector) public Color(Vector4 vector)
{ {
this.packedValue = Pack(ref vector); this.packedValue = Pack(ref vector);
@ -107,6 +111,7 @@ namespace ImageSharp
/// <param name="packed"> /// <param name="packed">
/// The packed value. /// The packed value.
/// </param> /// </param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Color(uint packed) public Color(uint packed)
{ {
this.packedValue = packed; this.packedValue = packed;

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

@ -13,14 +13,9 @@ namespace ImageSharp
/// for pixel buffers of type <typeparamref name="TColor"/>. /// for pixel buffers of type <typeparamref name="TColor"/>.
/// </summary> /// </summary>
/// <typeparam name="TColor">The pixel format.</typeparam> /// <typeparam name="TColor">The pixel format.</typeparam>
public unsafe class BulkPixelOperations<TColor> public class BulkPixelOperations<TColor>
where TColor : struct, IPixel<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> /// <summary>
/// Gets the global <see cref="BulkPixelOperations{TColor}"/> instance for the pixel type <typeparamref name="TColor"/> /// Gets the global <see cref="BulkPixelOperations{TColor}"/> instance for the pixel type <typeparamref name="TColor"/>
/// </summary> /// </summary>
@ -37,18 +32,14 @@ namespace ImageSharp
BufferSpan<TColor> destColors, BufferSpan<TColor> destColors,
int count) int count)
{ {
Vector4* sp = (Vector4*)sourceVectors.PointerAtOffset; ref Vector4 sourceRef = ref sourceVectors.DangerousGetPinnableReference();
byte* dp = (byte*)destColors; ref TColor destRef = ref destColors.DangerousGetPinnableReference();
for (int i = 0; i < count; i++) for (int i = 0; i < count; i++)
{ {
Vector4 v = Unsafe.Read<Vector4>(sp); ref Vector4 sp = ref Unsafe.Add(ref sourceRef, i);
TColor c = default(TColor); ref TColor dp = ref Unsafe.Add(ref destRef, i);
c.PackFromVector4(v); dp.PackFromVector4(sp);
Unsafe.Write(dp, c);
sp++;
dp += ColorSize;
} }
} }
@ -63,15 +54,14 @@ namespace ImageSharp
BufferSpan<Vector4> destVectors, BufferSpan<Vector4> destVectors,
int count) int count)
{ {
byte* sp = (byte*)sourceColors; ref TColor sourceRef = ref sourceColors.DangerousGetPinnableReference();
Vector4* dp = (Vector4*)destVectors.PointerAtOffset; ref Vector4 destRef = ref destVectors.DangerousGetPinnableReference();
for (int i = 0; i < count; i++) for (int i = 0; i < count; i++)
{ {
TColor c = Unsafe.Read<TColor>(sp); ref TColor sp = ref Unsafe.Add(ref sourceRef, i);
*dp = c.ToVector4(); ref Vector4 dp = ref Unsafe.Add(ref destRef, i);
sp += ColorSize; dp = sp.ToVector4();
dp++;
} }
} }
@ -86,16 +76,18 @@ namespace ImageSharp
BufferSpan<TColor> destColors, BufferSpan<TColor> destColors,
int count) int count)
{ {
byte* sp = (byte*)sourceBytes; ref byte sourceRef = ref sourceBytes.DangerousGetPinnableReference();
byte* dp = (byte*)destColors.PointerAtOffset; ref TColor destRef = ref destColors.DangerousGetPinnableReference();
for (int i = 0; i < count; i++) for (int i = 0; i < count; i++)
{ {
TColor c = default(TColor); int i3 = i * 3;
c.PackFromBytes(sp[0], sp[1], sp[2], 255); ref TColor dp = ref Unsafe.Add(ref destRef, i);
Unsafe.Write(dp, c); dp.PackFromBytes(
sp += 3; Unsafe.Add(ref sourceRef, i3),
dp += ColorSize; Unsafe.Add(ref sourceRef, i3 + 1),
Unsafe.Add(ref sourceRef, i3 + 2),
255);
} }
} }
@ -107,14 +99,13 @@ namespace ImageSharp
/// <param name="count">The number of pixels to convert.</param> /// <param name="count">The number of pixels to convert.</param>
internal virtual void ToXyzBytes(BufferSpan<TColor> sourceColors, BufferSpan<byte> destBytes, int count) internal virtual void ToXyzBytes(BufferSpan<TColor> sourceColors, BufferSpan<byte> destBytes, int count)
{ {
byte* sp = (byte*)sourceColors; ref TColor sourceRef = ref sourceColors.DangerousGetPinnableReference();
byte[] dest = destBytes.Array; byte[] dest = destBytes.Array;
for (int i = destBytes.Start; i < destBytes.Start + (count * 3); i += 3) for (int i = 0; i < count; i++)
{ {
TColor c = Unsafe.Read<TColor>(sp); ref TColor sp = ref Unsafe.Add(ref sourceRef, i);
c.ToXyzBytes(dest, i); sp.ToXyzBytes(dest, destBytes.Start + (i * 3));
sp += ColorSize;
} }
} }
@ -129,16 +120,18 @@ namespace ImageSharp
BufferSpan<TColor> destColors, BufferSpan<TColor> destColors,
int count) int count)
{ {
byte* sp = (byte*)sourceBytes; ref byte sourceRef = ref sourceBytes.DangerousGetPinnableReference();
byte* dp = (byte*)destColors.PointerAtOffset; ref TColor destRef = ref destColors.DangerousGetPinnableReference();
for (int i = 0; i < count; i++) for (int i = 0; i < count; i++)
{ {
TColor c = default(TColor); int i4 = i * 4;
c.PackFromBytes(sp[0], sp[1], sp[2], sp[3]); ref TColor dp = ref Unsafe.Add(ref destRef, i);
Unsafe.Write(dp, c); dp.PackFromBytes(
sp += 4; Unsafe.Add(ref sourceRef, i4),
dp += ColorSize; Unsafe.Add(ref sourceRef, i4 + 1),
Unsafe.Add(ref sourceRef, i4 + 2),
Unsafe.Add(ref sourceRef, i4 + 3));
} }
} }
@ -153,14 +146,13 @@ namespace ImageSharp
BufferSpan<byte> destBytes, BufferSpan<byte> destBytes,
int count) int count)
{ {
byte* sp = (byte*)sourceColors; ref TColor sourceRef = ref sourceColors.DangerousGetPinnableReference();
byte[] dest = destBytes.Array; byte[] dest = destBytes.Array;
for (int i = destBytes.Start; i < destBytes.Start + (count * 4); i += 4) for (int i = 0; i < count; i++)
{ {
TColor c = Unsafe.Read<TColor>(sp); ref TColor sp = ref Unsafe.Add(ref sourceRef, i);
c.ToXyzwBytes(dest, i); sp.ToXyzwBytes(dest, destBytes.Start + (i * 4));
sp += ColorSize;
} }
} }
@ -175,16 +167,18 @@ namespace ImageSharp
BufferSpan<TColor> destColors, BufferSpan<TColor> destColors,
int count) int count)
{ {
byte* sp = (byte*)sourceBytes; ref byte sourceRef = ref sourceBytes.DangerousGetPinnableReference();
byte* dp = (byte*)destColors.PointerAtOffset; ref TColor destRef = ref destColors.DangerousGetPinnableReference();
for (int i = 0; i < count; i++) for (int i = 0; i < count; i++)
{ {
TColor c = default(TColor); int i3 = i * 3;
c.PackFromBytes(sp[2], sp[1], sp[0], 255); ref TColor dp = ref Unsafe.Add(ref destRef, i);
Unsafe.Write(dp, c); dp.PackFromBytes(
sp += 3; Unsafe.Add(ref sourceRef, i3 + 2),
dp += ColorSize; Unsafe.Add(ref sourceRef, i3 + 1),
Unsafe.Add(ref sourceRef, i3),
255);
} }
} }
@ -196,14 +190,13 @@ namespace ImageSharp
/// <param name="count">The number of pixels to convert.</param> /// <param name="count">The number of pixels to convert.</param>
internal virtual void ToZyxBytes(BufferSpan<TColor> sourceColors, BufferSpan<byte> destBytes, int count) internal virtual void ToZyxBytes(BufferSpan<TColor> sourceColors, BufferSpan<byte> destBytes, int count)
{ {
byte* sp = (byte*)sourceColors; ref TColor sourceRef = ref sourceColors.DangerousGetPinnableReference();
byte[] dest = destBytes.Array; byte[] dest = destBytes.Array;
for (int i = destBytes.Start; i < destBytes.Start + (count * 3); i += 3) for (int i = 0; i < count; i++)
{ {
TColor c = Unsafe.Read<TColor>(sp); ref TColor sp = ref Unsafe.Add(ref sourceRef, i);
c.ToZyxBytes(dest, i); sp.ToZyxBytes(dest, destBytes.Start + (i * 3));
sp += ColorSize;
} }
} }
@ -218,16 +211,18 @@ namespace ImageSharp
BufferSpan<TColor> destColors, BufferSpan<TColor> destColors,
int count) int count)
{ {
byte* sp = (byte*)sourceBytes; ref byte sourceRef = ref sourceBytes.DangerousGetPinnableReference();
byte* dp = (byte*)destColors.PointerAtOffset; ref TColor destRef = ref destColors.DangerousGetPinnableReference();
for (int i = 0; i < count; i++) for (int i = 0; i < count; i++)
{ {
TColor c = default(TColor); int i4 = i * 4;
c.PackFromBytes(sp[2], sp[1], sp[0], sp[3]); ref TColor dp = ref Unsafe.Add(ref destRef, i);
Unsafe.Write(dp, c); dp.PackFromBytes(
sp += 4; Unsafe.Add(ref sourceRef, i4 + 2),
dp += ColorSize; Unsafe.Add(ref sourceRef, i4 + 1),
Unsafe.Add(ref sourceRef, i4),
Unsafe.Add(ref sourceRef, i4 + 3));
} }
} }
@ -242,14 +237,13 @@ namespace ImageSharp
BufferSpan<byte> destBytes, BufferSpan<byte> destBytes,
int count) int count)
{ {
byte* sp = (byte*)sourceColors; ref TColor sourceRef = ref sourceColors.DangerousGetPinnableReference();
byte[] dest = destBytes.Array; byte[] dest = destBytes.Array;
for (int i = destBytes.Start; i < destBytes.Start + (count * 4); i += 4) for (int i = 0; i < count; i++)
{ {
TColor c = Unsafe.Read<TColor>(sp); ref TColor sp = ref Unsafe.Add(ref sourceRef, i);
c.ToZyxwBytes(dest, i); sp.ToZyxwBytes(dest, destBytes.Start + (i * 4));
sp += ColorSize;
} }
} }
} }

99
src/ImageSharp/Common/Memory/PinnedBuffer{T}.cs → src/ImageSharp/Common/Memory/Buffer.cs

@ -1,4 +1,4 @@
// <copyright file="PinnedBuffer{T}.cs" company="James Jackson-South"> // <copyright file="Buffer{T}.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors. // Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
// </copyright> // </copyright>
@ -11,13 +11,18 @@ namespace ImageSharp
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
/// <summary> /// <summary>
/// Manages a pinned buffer of value type objects as a Disposable resource. /// Manages a buffer of value type objects as a Disposable resource.
/// The backing array is either pooled or comes from the outside. /// The backing array is either pooled or comes from the outside.
/// </summary> /// </summary>
/// <typeparam name="T">The value type.</typeparam> /// <typeparam name="T">The value type.</typeparam>
internal class PinnedBuffer<T> : IDisposable internal class Buffer<T> : IDisposable
where T : struct where T : struct
{ {
/// <summary>
/// A pointer to the first element of <see cref="Array"/> when pinned.
/// </summary>
private IntPtr pointer;
/// <summary> /// <summary>
/// A handle that allows to access the managed <see cref="Array"/> as an unmanaged memory by pinning. /// A handle that allows to access the managed <see cref="Array"/> as an unmanaged memory by pinning.
/// </summary> /// </summary>
@ -25,40 +30,38 @@ namespace ImageSharp
/// <summary> /// <summary>
/// A value indicating wheter <see cref="Array"/> should be returned to <see cref="PixelDataPool{T}"/> /// A value indicating wheter <see cref="Array"/> should be returned to <see cref="PixelDataPool{T}"/>
/// when disposing this <see cref="PinnedBuffer{T}"/> instance. /// when disposing this <see cref="Buffer{T}"/> instance.
/// </summary> /// </summary>
private bool isPoolingOwner; private bool isPoolingOwner;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="PinnedBuffer{T}"/> class. /// Initializes a new instance of the <see cref="Buffer{T}"/> class.
/// </summary> /// </summary>
/// <param name="length">The desired count of elements. (Minimum size for <see cref="Array"/>)</param> /// <param name="length">The desired count of elements. (Minimum size for <see cref="Array"/>)</param>
public PinnedBuffer(int length) public Buffer(int length)
{ {
this.Length = length; this.Length = length;
this.Array = PixelDataPool<T>.Rent(length); this.Array = PixelDataPool<T>.Rent(length);
this.isPoolingOwner = true; this.isPoolingOwner = true;
this.Pin();
} }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="PinnedBuffer{T}"/> class. /// Initializes a new instance of the <see cref="Buffer{T}"/> class.
/// </summary> /// </summary>
/// <param name="array">The array to pin.</param> /// <param name="array">The array to pin.</param>
public PinnedBuffer(T[] array) public Buffer(T[] array)
{ {
this.Length = array.Length; this.Length = array.Length;
this.Array = array; this.Array = array;
this.isPoolingOwner = false; this.isPoolingOwner = false;
this.Pin();
} }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="PinnedBuffer{T}"/> class. /// Initializes a new instance of the <see cref="Buffer{T}"/> class.
/// </summary> /// </summary>
/// <param name="array">The array to pin.</param> /// <param name="array">The array to pin.</param>
/// <param name="length">The count of "relevant" elements in 'array'.</param> /// <param name="length">The count of "relevant" elements in 'array'.</param>
public PinnedBuffer(T[] array, int length) public Buffer(T[] array, int length)
{ {
if (array.Length < length) if (array.Length < length)
{ {
@ -68,19 +71,18 @@ namespace ImageSharp
this.Length = length; this.Length = length;
this.Array = array; this.Array = array;
this.isPoolingOwner = false; this.isPoolingOwner = false;
this.Pin();
} }
/// <summary> /// <summary>
/// Finalizes an instance of the <see cref="PinnedBuffer{T}"/> class. /// Finalizes an instance of the <see cref="Buffer{T}"/> class.
/// </summary> /// </summary>
~PinnedBuffer() ~Buffer()
{ {
this.UnPin(); this.UnPin();
} }
/// <summary> /// <summary>
/// Gets a value indicating whether this <see cref="PinnedBuffer{T}"/> instance is disposed, or has lost ownership of <see cref="Array"/>. /// Gets a value indicating whether this <see cref="Buffer{T}"/> instance is disposed, or has lost ownership of <see cref="Array"/>.
/// </summary> /// </summary>
public bool IsDisposedOrLostArrayOwnership { get; private set; } public bool IsDisposedOrLostArrayOwnership { get; private set; }
@ -94,11 +96,6 @@ namespace ImageSharp
/// </summary> /// </summary>
public T[] Array { get; private set; } public T[] Array { get; private set; }
/// <summary>
/// Gets a pointer to the pinned <see cref="Array"/>.
/// </summary>
public IntPtr Pointer { get; private set; }
/// <summary> /// <summary>
/// Gets a <see cref="BufferSpan{T}"/> to the backing buffer. /// Gets a <see cref="BufferSpan{T}"/> to the backing buffer.
/// </summary> /// </summary>
@ -109,37 +106,35 @@ namespace ImageSharp
/// </summary> /// </summary>
/// <param name="index">The index</param> /// <param name="index">The index</param>
/// <returns>The reference to the specified element</returns> /// <returns>The reference to the specified element</returns>
public unsafe ref T this[int index] public ref T this[int index]
{ {
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
get get
{ {
DebugGuard.MustBeLessThan(index, this.Length, nameof(index)); DebugGuard.MustBeLessThan(index, this.Length, nameof(index));
return ref this.Array[index];
byte* ptr = (byte*)this.Pointer + BufferSpan.SizeOf<T>(index);
return ref Unsafe.AsRef<T>(ptr);
} }
} }
/// <summary> /// <summary>
/// Converts <see cref="PinnedBuffer{T}"/> to an <see cref="BufferSpan{T}"/>. /// Converts <see cref="Buffer{T}"/> to an <see cref="BufferSpan{T}"/>.
/// </summary> /// </summary>
/// <param name="buffer">The <see cref="PinnedBuffer{T}"/> to convert.</param> /// <param name="buffer">The <see cref="Buffer{T}"/> to convert.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe implicit operator BufferSpan<T>(PinnedBuffer<T> buffer) public static implicit operator BufferSpan<T>(Buffer<T> buffer)
{ {
return new BufferSpan<T>(buffer.Array, (void*)buffer.Pointer, 0, buffer.Length); return new BufferSpan<T>(buffer.Array, 0, buffer.Length);
} }
/// <summary> /// <summary>
/// Creates a clean instance of <see cref="PinnedBuffer{T}"/> initializing it's elements with 'default(T)'. /// Creates a clean instance of <see cref="Buffer{T}"/> initializing it's elements with 'default(T)'.
/// </summary> /// </summary>
/// <param name="count">The desired count of elements. (Minimum size for <see cref="Array"/>)</param> /// <param name="count">The desired count of elements. (Minimum size for <see cref="Array"/>)</param>
/// <returns>The <see cref="PinnedBuffer{T}"/> instance</returns> /// <returns>The <see cref="Buffer{T}"/> instance</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static PinnedBuffer<T> CreateClean(int count) public static Buffer<T> CreateClean(int count)
{ {
PinnedBuffer<T> buffer = new PinnedBuffer<T>(count); Buffer<T> buffer = new Buffer<T>(count);
buffer.Clear(); buffer.Clear();
return buffer; return buffer;
} }
@ -150,9 +145,9 @@ namespace ImageSharp
/// <param name="start">The start</param> /// <param name="start">The start</param>
/// <returns>The <see cref="BufferSpan{T}"/></returns> /// <returns>The <see cref="BufferSpan{T}"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe BufferSpan<T> Slice(int start) public BufferSpan<T> Slice(int start)
{ {
return new BufferSpan<T>(this.Array, (void*)this.Pointer, start, this.Length - start); return new BufferSpan<T>(this.Array, start, this.Length - start);
} }
/// <summary> /// <summary>
@ -162,13 +157,13 @@ namespace ImageSharp
/// <param name="length">The length of the slice</param> /// <param name="length">The length of the slice</param>
/// <returns>The <see cref="BufferSpan{T}"/></returns> /// <returns>The <see cref="BufferSpan{T}"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe BufferSpan<T> Slice(int start, int length) public BufferSpan<T> Slice(int start, int length)
{ {
return new BufferSpan<T>(this.Array, (void*)this.Pointer, start, length); return new BufferSpan<T>(this.Array, start, length);
} }
/// <summary> /// <summary>
/// Disposes the <see cref="PinnedBuffer{T}"/> instance by unpinning the array, and returning the pooled buffer when necessary. /// Disposes the <see cref="Buffer{T}"/> instance by unpinning the array, and returning the pooled buffer when necessary.
/// </summary> /// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Dispose() public void Dispose()
@ -199,11 +194,11 @@ namespace ImageSharp
/// </summary> /// </summary>
/// <returns>The unpinned <see cref="Array"/></returns> /// <returns>The unpinned <see cref="Array"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public T[] UnPinAndTakeArrayOwnership() public T[] TakeArrayOwnership()
{ {
if (this.IsDisposedOrLostArrayOwnership) if (this.IsDisposedOrLostArrayOwnership)
{ {
throw new InvalidOperationException("UnPinAndTakeArrayOwnership() is invalid: either PinnedBuffer<T> is disposed or UnPinAndTakeArrayOwnership() has been called multiple times!"); throw new InvalidOperationException("TakeArrayOwnership() is invalid: either Buffer<T> is disposed or TakeArrayOwnership() has been called multiple times!");
} }
this.IsDisposedOrLostArrayOwnership = true; this.IsDisposedOrLostArrayOwnership = true;
@ -220,17 +215,29 @@ namespace ImageSharp
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Clear() public void Clear()
{ {
((BufferSpan<T>)this).Clear(); this.Span.Clear();
} }
/// <summary> /// <summary>
/// Pins <see cref="Array"/>. /// Pins <see cref="Array"/>.
/// </summary> /// </summary>
/// <returns>The pinned pointer</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private void Pin() public IntPtr Pin()
{ {
this.handle = GCHandle.Alloc(this.Array, GCHandleType.Pinned); if (this.IsDisposedOrLostArrayOwnership)
this.Pointer = this.handle.AddrOfPinnedObject(); {
throw new InvalidOperationException(
"Pin() is invalid on a buffer with IsDisposedOrLostArrayOwnership == true!");
}
if (this.pointer == IntPtr.Zero)
{
this.handle = GCHandle.Alloc(this.Array, GCHandleType.Pinned);
this.pointer = this.handle.AddrOfPinnedObject();
}
return this.pointer;
} }
/// <summary> /// <summary>
@ -239,13 +246,13 @@ namespace ImageSharp
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private void UnPin() private void UnPin()
{ {
if (this.Pointer == IntPtr.Zero || !this.handle.IsAllocated) if (this.pointer == IntPtr.Zero || !this.handle.IsAllocated)
{ {
return; return;
} }
this.handle.Free(); this.handle.Free();
this.Pointer = IntPtr.Zero; this.pointer = IntPtr.Zero;
} }
} }
} }

22
src/ImageSharp/Common/Memory/PinnedImageBuffer{T}.cs → src/ImageSharp/Common/Memory/Buffer2D.cs

@ -1,4 +1,4 @@
// <copyright file="PinnedImageBuffer{T}.cs" company="James Jackson-South"> // <copyright file="Buffer2D{T}.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors. // Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
// </copyright> // </copyright>
@ -9,19 +9,19 @@ namespace ImageSharp
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
/// <summary> /// <summary>
/// Represents a pinned buffer of value type objects /// Represents a buffer of value type objects
/// interpreted as a 2D region of <see cref="Width"/> x <see cref="Height"/> elements. /// interpreted as a 2D region of <see cref="Width"/> x <see cref="Height"/> elements.
/// </summary> /// </summary>
/// <typeparam name="T">The value type.</typeparam> /// <typeparam name="T">The value type.</typeparam>
internal class PinnedImageBuffer<T> : PinnedBuffer<T>, IPinnedImageBuffer<T> internal class Buffer2D<T> : Buffer<T>, IBuffer2D<T>
where T : struct where T : struct
{ {
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="PinnedImageBuffer{T}"/> class. /// Initializes a new instance of the <see cref="Buffer2D{T}"/> class.
/// </summary> /// </summary>
/// <param name="width">The number of elements in a row</param> /// <param name="width">The number of elements in a row</param>
/// <param name="height">The number of rows</param> /// <param name="height">The number of rows</param>
public PinnedImageBuffer(int width, int height) public Buffer2D(int width, int height)
: base(width * height) : base(width * height)
{ {
this.Width = width; this.Width = width;
@ -29,12 +29,12 @@ namespace ImageSharp
} }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="PinnedImageBuffer{T}"/> class. /// Initializes a new instance of the <see cref="Buffer2D{T}"/> class.
/// </summary> /// </summary>
/// <param name="array">The array to pin</param> /// <param name="array">The array to pin</param>
/// <param name="width">The number of elements in a row</param> /// <param name="width">The number of elements in a row</param>
/// <param name="height">The number of rows</param> /// <param name="height">The number of rows</param>
public PinnedImageBuffer(T[] array, int width, int height) public Buffer2D(T[] array, int width, int height)
: base(array, width * height) : base(array, width * height)
{ {
this.Width = width; this.Width = width;
@ -63,14 +63,14 @@ namespace ImageSharp
} }
/// <summary> /// <summary>
/// Creates a clean instance of <see cref="PinnedImageBuffer{T}"/> initializing it's elements with 'default(T)'. /// Creates a clean instance of <see cref="Buffer2D{T}"/> initializing it's elements with 'default(T)'.
/// </summary> /// </summary>
/// <param name="width">The number of elements in a row</param> /// <param name="width">The number of elements in a row</param>
/// <param name="height">The number of rows</param> /// <param name="height">The number of rows</param>
/// <returns>The <see cref="PinnedBuffer{T}"/> instance</returns> /// <returns>The <see cref="Buffer{T}"/> instance</returns>
public static PinnedImageBuffer<T> CreateClean(int width, int height) public static Buffer2D<T> CreateClean(int width, int height)
{ {
PinnedImageBuffer<T> buffer = new PinnedImageBuffer<T>(width, height); Buffer2D<T> buffer = new Buffer2D<T>(width, height);
buffer.Clear(); buffer.Clear();
return buffer; return buffer;
} }

10
src/ImageSharp/Common/Memory/PinnedImageBufferExtensions.cs → src/ImageSharp/Common/Memory/Buffer2DExtensions.cs

@ -1,4 +1,4 @@
// <copyright file="PinnedImageBufferExtensions{T}.cs" company="James Jackson-South"> // <copyright file="Buffer2DExtensions{T}.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors. // Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
// </copyright> // </copyright>
@ -9,9 +9,9 @@ namespace ImageSharp
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
/// <summary> /// <summary>
/// Defines extension methods for <see cref="IPinnedImageBuffer{T}"/>. /// Defines extension methods for <see cref="IBuffer2D{T}"/>.
/// </summary> /// </summary>
internal static class PinnedImageBufferExtensions internal static class Buffer2DExtensions
{ {
/// <summary> /// <summary>
/// Gets a <see cref="BufferSpan{T}"/> to the row 'y' beginning from the pixel at 'x'. /// Gets a <see cref="BufferSpan{T}"/> to the row 'y' beginning from the pixel at 'x'.
@ -22,7 +22,7 @@ namespace ImageSharp
/// <typeparam name="T">The element type</typeparam> /// <typeparam name="T">The element type</typeparam>
/// <returns>The <see cref="BufferSpan{T}"/></returns> /// <returns>The <see cref="BufferSpan{T}"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static BufferSpan<T> GetRowSpan<T>(this IPinnedImageBuffer<T> buffer, int x, int y) public static BufferSpan<T> GetRowSpan<T>(this IBuffer2D<T> buffer, int x, int y)
where T : struct where T : struct
{ {
return buffer.Span.Slice((y * buffer.Width) + x, buffer.Width - x); return buffer.Span.Slice((y * buffer.Width) + x, buffer.Width - x);
@ -36,7 +36,7 @@ namespace ImageSharp
/// <typeparam name="T">The element type</typeparam> /// <typeparam name="T">The element type</typeparam>
/// <returns>The <see cref="BufferSpan{T}"/></returns> /// <returns>The <see cref="BufferSpan{T}"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static BufferSpan<T> GetRowSpan<T>(this IPinnedImageBuffer<T> buffer, int y) public static BufferSpan<T> GetRowSpan<T>(this IBuffer2D<T> buffer, int y)
where T : struct where T : struct
{ {
return buffer.Span.Slice(y * buffer.Width, buffer.Width); return buffer.Span.Slice(y * buffer.Width, buffer.Width);

96
src/ImageSharp/Common/Memory/BufferSpan.cs

@ -6,7 +6,6 @@
namespace ImageSharp namespace ImageSharp
{ {
using System; using System;
using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
@ -15,60 +14,49 @@ namespace ImageSharp
/// </summary> /// </summary>
internal static class BufferSpan internal static class BufferSpan
{ {
/// <summary>
/// It's worth to use Marshal.Copy() or Buffer.BlockCopy() over this size.
/// </summary>
private const int ByteCountThreshold = 1024;
/// <summary> /// <summary>
/// Copy 'count' number of elements of the same type from 'source' to 'dest' /// Copy 'count' number of elements of the same type from 'source' to 'dest'
/// </summary> /// </summary>
/// <typeparam name="T">The element type.</typeparam> /// <typeparam name="T">The element type.</typeparam>
/// <param name="source">The input <see cref="BufferSpan{T}"/></param> /// <param name="source">The <see cref="BufferSpan{T}"/> to copy elements from.</param>
/// <param name="destination">The destination <see cref="BufferSpan{T}"/>.</param> /// <param name="destination">The destination <see cref="BufferSpan{T}"/>.</param>
/// <param name="count">The number of elements to copy</param> /// <param name="count">The number of elements to copy</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Copy<T>(BufferSpan<T> source, BufferSpan<T> destination, int count) public static unsafe void Copy<T>(BufferSpan<T> source, BufferSpan<T> destination, int count)
where T : struct where T : struct
{ {
CopyImpl(source, destination, count); DebugGuard.MustBeLessThanOrEqualTo(count, source.Length, nameof(count));
} DebugGuard.MustBeLessThanOrEqualTo(count, destination.Length, nameof(count));
/// <summary> ref byte srcRef = ref Unsafe.As<T, byte>(ref source.DangerousGetPinnableReference());
/// Copy 'countInSource' elements of <typeparamref name="T"/> from 'source' into the raw byte buffer 'destination'. ref byte destRef = ref Unsafe.As<T, byte>(ref destination.DangerousGetPinnableReference());
/// </summary>
/// <typeparam name="T">The element type.</typeparam> int byteCount = Unsafe.SizeOf<T>() * count;
/// <param name="source">The source buffer of <typeparamref name="T"/> elements to copy from.</param>
/// <param name="destination">The destination buffer.</param> // TODO: Use unfixed Unsafe.CopyBlock(ref T, ref T, int) for small blocks, when it gets available!
/// <param name="countInSource">The number of elements to copy from 'source'</param> fixed (byte* pSrc = &srcRef)
[MethodImpl(MethodImplOptions.AggressiveInlining)] fixed (byte* pDest = &destRef)
public static void Copy<T>(BufferSpan<T> source, BufferSpan<byte> destination, int countInSource) {
where T : struct #if NETSTANDARD1_1
{ Unsafe.CopyBlock(pDest, pSrc, (uint)byteCount);
CopyImpl(source, destination, countInSource); #else
int destLength = destination.Length * Unsafe.SizeOf<T>();
Buffer.MemoryCopy(pSrc, pDest, destLength, byteCount);
#endif
}
} }
/// <summary> /// <summary>
/// Copy 'countInDest' number of <typeparamref name="T"/> elements into 'dest' from a raw byte buffer defined by 'source'. /// Copy all elements of 'source' into 'destination'.
/// </summary> /// </summary>
/// <typeparam name="T">The element type.</typeparam> /// <typeparam name="T">The element type.</typeparam>
/// <param name="source">The raw source buffer to copy from"/></param> /// <param name="source">The <see cref="BufferSpan{T}"/> to copy elements from.</param>
/// <param name="destination">The destination buffer"/></param> /// <param name="destination">The destination <see cref="BufferSpan{T}"/>.</param>
/// <param name="countInDest">The number of <typeparamref name="T"/> elements to copy.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe void Copy<T>(BufferSpan<byte> source, BufferSpan<T> destination, int countInDest) public static void Copy<T>(BufferSpan<T> source, BufferSpan<T> destination)
where T : struct where T : struct
{ {
int byteCount = SizeOf<T>(countInDest); Copy(source, destination, source.Length);
if (byteCount > (int)ByteCountThreshold)
{
Marshal.Copy(source.Array, source.Start, destination.PointerAtOffset, byteCount);
}
else
{
Unsafe.CopyBlock((void*)destination.PointerAtOffset, (void*)source.PointerAtOffset, (uint)byteCount);
}
} }
/// <summary> /// <summary>
@ -91,39 +79,5 @@ namespace ImageSharp
public static uint USizeOf<T>(int count) public static uint USizeOf<T>(int count)
where T : struct where T : struct
=> (uint)SizeOf<T>(count); => (uint)SizeOf<T>(count);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static unsafe void CopyImpl<T, TDest>(BufferSpan<T> source, BufferSpan<TDest> destination, int count)
where T : struct
where TDest : struct
{
int byteCount = SizeOf<T>(count);
if (byteCount > ByteCountThreshold)
{
if (Unsafe.SizeOf<T>() == sizeof(long))
{
Marshal.Copy(Unsafe.As<long[]>(source.Array), source.Start, destination.PointerAtOffset, count);
return;
}
else if (Unsafe.SizeOf<T>() == sizeof(int))
{
Marshal.Copy(Unsafe.As<int[]>(source.Array), source.Start, destination.PointerAtOffset, count);
return;
}
else if (Unsafe.SizeOf<T>() == sizeof(short))
{
Marshal.Copy(Unsafe.As<short[]>(source.Array), source.Start, destination.PointerAtOffset, count);
return;
}
else if (Unsafe.SizeOf<T>() == sizeof(byte))
{
Marshal.Copy(Unsafe.As<byte[]>(source.Array), source.Start, destination.PointerAtOffset, count);
return;
}
}
Unsafe.CopyBlock((void*)destination.PointerAtOffset, (void*)source.PointerAtOffset, (uint)byteCount);
}
} }
} }

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

@ -12,7 +12,7 @@ namespace ImageSharp
/// <summary> /// <summary>
/// Represents a contiguous region of a pinned managed array. /// Represents a contiguous region of a pinned managed array.
/// The array is usually owned by a <see cref="PinnedBuffer{T}"/> instance. /// The array is usually owned by a <see cref="Buffer{T}"/> instance.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// <see cref="BufferSpan{T}"/> is very similar to corefx System.Span&lt;T&gt;, and we try to maintain a compatible API. /// <see cref="BufferSpan{T}"/> is very similar to corefx System.Span&lt;T&gt;, and we try to maintain a compatible API.
@ -30,13 +30,12 @@ namespace ImageSharp
/// Initializes a new instance of the <see cref="BufferSpan{T}"/> struct from a pinned array and an start. /// Initializes a new instance of the <see cref="BufferSpan{T}"/> struct from a pinned array and an start.
/// </summary> /// </summary>
/// <param name="array">The pinned array</param> /// <param name="array">The pinned array</param>
/// <param name="pointerToArray">Pointer to the beginning of the array</param>
/// <param name="start">The index at which to begin the span.</param> /// <param name="start">The index at which to begin the span.</param>
/// <param name="length">The length</param> /// <param name="length">The length</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public BufferSpan(T[] array, void* pointerToArray, int start, int length) public BufferSpan(T[] array, int start, int length)
{ {
GuardArrayAndPointer(array, pointerToArray); GuardArray(array);
DebugGuard.MustBeLessThanOrEqualTo(start, array.Length, nameof(start)); DebugGuard.MustBeLessThanOrEqualTo(start, array.Length, nameof(start));
DebugGuard.MustBeLessThanOrEqualTo(length, array.Length - start, nameof(length)); DebugGuard.MustBeLessThanOrEqualTo(length, array.Length - start, nameof(length));
@ -44,45 +43,40 @@ namespace ImageSharp
this.Array = array; this.Array = array;
this.Length = length; this.Length = length;
this.Start = start; this.Start = start;
this.PointerAtOffset = (IntPtr)pointerToArray + (Unsafe.SizeOf<T>() * start);
} }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="BufferSpan{T}"/> struct from a pinned array and an start. /// Initializes a new instance of the <see cref="BufferSpan{T}"/> struct from a pinned array and an start.
/// </summary> /// </summary>
/// <param name="array">The pinned array</param> /// <param name="array">The pinned array</param>
/// <param name="pointerToArray">Pointer to the beginning of the array</param>
/// <param name="start">The index at which to begin the span.</param> /// <param name="start">The index at which to begin the span.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public BufferSpan(T[] array, void* pointerToArray, int start) public BufferSpan(T[] array, int start)
{ {
GuardArrayAndPointer(array, pointerToArray); GuardArray(array);
DebugGuard.MustBeLessThanOrEqualTo(start, array.Length, nameof(start)); DebugGuard.MustBeLessThanOrEqualTo(start, array.Length, nameof(start));
this.Array = array; this.Array = array;
this.Length = array.Length - start; this.Length = array.Length - start;
this.Start = start; this.Start = start;
this.PointerAtOffset = (IntPtr)pointerToArray + (Unsafe.SizeOf<T>() * start);
} }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="BufferSpan{T}"/> struct from a pinned array. /// Initializes a new instance of the <see cref="BufferSpan{T}"/> struct from a pinned array.
/// </summary> /// </summary>
/// <param name="array">The pinned array</param> /// <param name="array">The pinned array</param>
/// <param name="pointerToArray">Pointer to the start of 'array'</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public BufferSpan(T[] array, void* pointerToArray) public BufferSpan(T[] array)
{ {
GuardArrayAndPointer(array, pointerToArray); GuardArray(array);
this.Array = array; this.Array = array;
this.Start = 0; this.Start = 0;
this.Length = array.Length; this.Length = array.Length;
this.PointerAtOffset = (IntPtr)pointerToArray;
} }
/// <summary> /// <summary>
/// Gets the backing array /// Gets the backing array.
/// </summary> /// </summary>
public T[] Array { get; private set; } public T[] Array { get; private set; }
@ -101,11 +95,6 @@ namespace ImageSharp
/// </summary> /// </summary>
public int ByteOffset => this.Start * Unsafe.SizeOf<T>(); public int ByteOffset => this.Start * Unsafe.SizeOf<T>();
/// <summary>
/// Gets the pointer to the offseted array position
/// </summary>
public IntPtr PointerAtOffset { get; private set; }
/// <summary> /// <summary>
/// Returns a reference to specified element of the span. /// Returns a reference to specified element of the span.
/// </summary> /// </summary>
@ -117,45 +106,36 @@ namespace ImageSharp
get get
{ {
DebugGuard.MustBeLessThan(index, this.Length, nameof(index)); DebugGuard.MustBeLessThan(index, this.Length, nameof(index));
ref T startRef = ref this.DangerousGetPinnableReference();
byte* ptr = (byte*)this.PointerAtOffset + BufferSpan.SizeOf<T>(index); return ref Unsafe.Add(ref startRef, index);
return ref Unsafe.AsRef<T>(ptr);
} }
} }
/// <summary> /// <summary>
/// Convertes <see cref="BufferSpan{T}"/> instance to a raw 'void*' pointer /// Converts generic <see cref="BufferSpan{T}"/> to a <see cref="BufferSpan{T}"/> of bytes
/// </summary> /// setting it's <see cref="Start"/> and <see cref="Length"/> to correct values.
/// <param name="bufferSpan">The <see cref="BufferSpan{T}"/> to convert</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static explicit operator void*(BufferSpan<T> bufferSpan)
{
return (void*)bufferSpan.PointerAtOffset;
}
/// <summary>
/// Converts <see cref="BufferSpan{T}"/> instance to a raw 'byte*' pointer
/// </summary> /// </summary>
/// <param name="bufferSpan">The <see cref="BufferSpan{T}"/> to convert</param> /// <returns>The span of bytes</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static explicit operator byte*(BufferSpan<T> bufferSpan) public BufferSpan<byte> AsBytes()
{ {
return (byte*)bufferSpan.PointerAtOffset; BufferSpan<byte> result = default(BufferSpan<byte>);
result.Array = Unsafe.As<byte[]>(this.Array);
result.Start = this.Start * Unsafe.SizeOf<T>();
result.Length = this.Length * Unsafe.SizeOf<T>();
return result;
} }
/// <summary> /// <summary>
/// Converts generic <see cref="BufferSpan{T}"/> to a <see cref="BufferSpan{T}"/> of bytes /// Returns a reference to the 0th element of the Span. If the Span is empty, returns a reference to the location where the 0th element
/// setting it's <see cref="Start"/> and <see cref="PointerAtOffset"/> to correct values. /// would have been stored. Such a reference can be used for pinning but must never be dereferenced.
/// </summary> /// </summary>
/// <param name="source">The <see cref="BufferSpan{T}"/> to convert</param> /// <returns>The reference to the 0th element</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static explicit operator BufferSpan<byte>(BufferSpan<T> source) public ref T DangerousGetPinnableReference()
{ {
BufferSpan<byte> result = default(BufferSpan<byte>); ref T origin = ref this.Array[0];
result.Array = Unsafe.As<byte[]>(source.Array); return ref Unsafe.Add(ref origin, this.Start);
result.Start = source.Start * Unsafe.SizeOf<T>();
result.PointerAtOffset = source.PointerAtOffset;
return result;
} }
/// <summary> /// <summary>
@ -171,7 +151,6 @@ namespace ImageSharp
BufferSpan<T> result = default(BufferSpan<T>); BufferSpan<T> result = default(BufferSpan<T>);
result.Array = this.Array; result.Array = this.Array;
result.Start = this.Start + start; result.Start = this.Start + start;
result.PointerAtOffset = this.PointerAtOffset + (Unsafe.SizeOf<T>() * start);
result.Length = this.Length - start; result.Length = this.Length - start;
return result; return result;
} }
@ -191,7 +170,6 @@ namespace ImageSharp
BufferSpan<T> result = default(BufferSpan<T>); BufferSpan<T> result = default(BufferSpan<T>);
result.Array = this.Array; result.Array = this.Array;
result.Start = this.Start + start; result.Start = this.Start + start;
result.PointerAtOffset = this.PointerAtOffset + (Unsafe.SizeOf<T>() * start);
result.Length = length; result.Length = length;
return result; return result;
} }
@ -205,14 +183,8 @@ namespace ImageSharp
{ {
DebugGuard.MustBeLessThanOrEqualTo(count, this.Length, nameof(count)); DebugGuard.MustBeLessThanOrEqualTo(count, this.Length, nameof(count));
if (count < 256) // TODO: Use Unsafe.InitBlock(ref T) for small arrays, when it get's official
{ System.Array.Clear(this.Array, this.Start, count);
Unsafe.InitBlock((void*)this.PointerAtOffset, 0, BufferSpan.USizeOf<T>(count));
}
else
{
System.Array.Clear(this.Array, this.Start, count);
}
} }
/// <summary> /// <summary>
@ -225,13 +197,9 @@ namespace ImageSharp
} }
[Conditional("DEBUG")] [Conditional("DEBUG")]
private static void GuardArrayAndPointer(T[] array, void* pointerToArray) private static void GuardArray(T[] array)
{ {
DebugGuard.NotNull(array, nameof(array)); DebugGuard.NotNull(array, nameof(array));
DebugGuard.IsFalse(
pointerToArray == (void*)0,
nameof(pointerToArray),
"pointerToArray should not be null pointer!");
} }
} }
} }

4
src/ImageSharp/Common/Memory/IPinnedImageBuffer{T}.cs → src/ImageSharp/Common/Memory/IBuffer2D.cs

@ -1,4 +1,4 @@
// <copyright file="IPinnedImageBuffer{T}.cs" company="James Jackson-South"> // <copyright file="IBuffer2D{T}.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors. // Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
// </copyright> // </copyright>
@ -10,7 +10,7 @@ namespace ImageSharp
/// interpreted as a 2D region of <see cref="Width"/> x <see cref="Height"/> elements. /// interpreted as a 2D region of <see cref="Width"/> x <see cref="Height"/> elements.
/// </summary> /// </summary>
/// <typeparam name="T">The value type.</typeparam> /// <typeparam name="T">The value type.</typeparam>
internal interface IPinnedImageBuffer<T> internal interface IBuffer2D<T>
where T : struct where T : struct
{ {
/// <summary> /// <summary>

11
src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs

@ -307,7 +307,8 @@ namespace ImageSharp.Formats
rgbBytes.Reset(); rgbBytes.Reset();
pixels.CopyRGBBytesStretchedTo(rgbBytes, y, x); pixels.CopyRGBBytesStretchedTo(rgbBytes, y, x);
byte* data = (byte*)rgbBytes.DataPointer; ref byte data0 = ref rgbBytes.Bytes[0];
int dataIdx = 0;
for (int j = 0; j < 8; j++) for (int j = 0; j < 8; j++)
{ {
@ -315,9 +316,9 @@ namespace ImageSharp.Formats
for (int i = 0; i < 8; i++) for (int i = 0; i < 8; i++)
{ {
// Convert returned bytes into the YCbCr color space. Assume RGBA // Convert returned bytes into the YCbCr color space. Assume RGBA
int r = data[0]; int r = Unsafe.Add(ref data0, dataIdx);
int g = data[1]; int g = Unsafe.Add(ref data0, dataIdx + 1);
int b = data[2]; int b = Unsafe.Add(ref data0, dataIdx + 2);
// Speed up the algorithm by removing floating point calculation // Speed up the algorithm by removing floating point calculation
// Scale by 65536, add .5F and truncate value. We use bit shifting to divide the result // Scale by 65536, add .5F and truncate value. We use bit shifting to divide the result
@ -343,7 +344,7 @@ namespace ImageSharp.Formats
cbBlockRaw[index] = cb; cbBlockRaw[index] = cb;
crBlockRaw[index] = cr; crBlockRaw[index] = cr;
data += 3; dataIdx += 3;
} }
} }
} }

45
src/ImageSharp/Formats/Jpeg/Utils/JpegUtils.cs

@ -6,6 +6,7 @@ namespace ImageSharp.Formats.Jpg
{ {
using System; using System;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
/// <summary> /// <summary>
/// Jpeg specific utilities and extension methods /// Jpeg specific utilities and extension methods
@ -33,19 +34,6 @@ namespace ImageSharp.Formats.Jpg
StretchPixels(dest, stretchFromX, stretchFromY); StretchPixels(dest, stretchFromX, stretchFromY);
} }
/// <summary>
/// Copy an RGB value
/// </summary>
/// <param name="source">Source pointer</param>
/// <param name="dest">Destination pointer</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void CopyRgb(byte* source, byte* dest)
{
*dest++ = *source++; // R
*dest++ = *source++; // G
*dest = *source; // B
}
// Nothing to stretch if (fromX, fromY) is outside the area, or is at (0,0) // Nothing to stretch if (fromX, fromY) is outside the area, or is at (0,0)
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool IsInvalidStretchStartingPosition<TColor>(PixelArea<TColor> area, int fromX, int fromY) private static bool IsInvalidStretchStartingPosition<TColor>(PixelArea<TColor> area, int fromX, int fromY)
@ -64,31 +52,38 @@ namespace ImageSharp.Formats.Jpg
for (int y = 0; y < fromY; y++) for (int y = 0; y < fromY; y++)
{ {
byte* ptrBase = (byte*)area.DataPointer + (y * area.RowStride); ref RGB24 ptrBase = ref GetRowStart(area, y);
for (int x = fromX; x < area.Width; x++) for (int x = fromX; x < area.Width; x++)
{ {
byte* prevPtr = ptrBase + ((x - 1) * 3); // Copy the left neighbour pixel to the current one
byte* currPtr = ptrBase + (x * 3); Unsafe.Add(ref ptrBase, x) = Unsafe.Add(ref ptrBase, x - 1);
CopyRgb(prevPtr, currPtr);
} }
} }
for (int y = fromY; y < area.Height; y++) for (int y = fromY; y < area.Height; y++)
{ {
byte* currBase = (byte*)area.DataPointer + (y * area.RowStride); ref RGB24 currBase = ref GetRowStart(area, y);
byte* prevBase = (byte*)area.DataPointer + ((y - 1) * area.RowStride); ref RGB24 prevBase = ref GetRowStart(area, y - 1);
for (int x = 0; x < area.Width; x++) for (int x = 0; x < area.Width; x++)
{ {
int x3 = 3 * x; // Copy the top neighbour pixel to the current one
byte* currPtr = currBase + x3; Unsafe.Add(ref currBase, x) = Unsafe.Add(ref prevBase, x);
byte* prevPtr = prevBase + x3;
CopyRgb(prevPtr, currPtr);
} }
} }
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static ref RGB24 GetRowStart<TColor>(PixelArea<TColor> area, int y)
where TColor : struct, IPixel<TColor>
{
return ref Unsafe.As<byte, RGB24>(ref area.GetRowSpan(y).DangerousGetPinnableReference());
}
[StructLayout(LayoutKind.Sequential, Size = 3)]
private struct RGB24
{
}
} }
} }

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

@ -15,14 +15,9 @@ namespace ImageSharp
/// Provides per-pixel access to generic <see cref="Image{TColor}"/> pixels. /// Provides per-pixel access to generic <see cref="Image{TColor}"/> pixels.
/// </summary> /// </summary>
/// <typeparam name="TColor">The pixel format.</typeparam> /// <typeparam name="TColor">The pixel format.</typeparam>
public sealed unsafe class PixelAccessor<TColor> : IDisposable, IPinnedImageBuffer<TColor> public sealed class PixelAccessor<TColor> : IDisposable, IBuffer2D<TColor>
where TColor : struct, IPixel<TColor> where TColor : struct, IPixel<TColor>
{ {
/// <summary>
/// The position of the first pixel in the image.
/// </summary>
private byte* pixelsBase;
/// <summary> /// <summary>
/// A value indicating whether this instance of the given entity has been disposed. /// A value indicating whether this instance of the given entity has been disposed.
/// </summary> /// </summary>
@ -35,9 +30,9 @@ namespace ImageSharp
private bool isDisposed; private bool isDisposed;
/// <summary> /// <summary>
/// The <see cref="PinnedBuffer{T}"/> containing the pixel data. /// The <see cref="Buffer{T}"/> containing the pixel data.
/// </summary> /// </summary>
private PinnedImageBuffer<TColor> pixelBuffer; private Buffer2D<TColor> pixelBuffer;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="PixelAccessor{TColor}"/> class. /// Initializes a new instance of the <see cref="PixelAccessor{TColor}"/> class.
@ -59,7 +54,7 @@ namespace ImageSharp
/// <param name="width">The width of the image represented by the pixel buffer.</param> /// <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="height">The height of the image represented by the pixel buffer.</param>
public PixelAccessor(int width, int height) public PixelAccessor(int width, int height)
: this(width, height, PinnedImageBuffer<TColor>.CreateClean(width, height)) : this(width, height, Buffer2D<TColor>.CreateClean(width, height))
{ {
} }
@ -69,7 +64,7 @@ namespace ImageSharp
/// <param name="width">The width of the image represented by the pixel buffer.</param> /// <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="height">The height of the image represented by the pixel buffer.</param>
/// <param name="pixels">The pixel buffer.</param> /// <param name="pixels">The pixel buffer.</param>
private PixelAccessor(int width, int height, PinnedImageBuffer<TColor> pixels) private PixelAccessor(int width, int height, Buffer2D<TColor> pixels)
{ {
Guard.NotNull(pixels, nameof(pixels)); Guard.NotNull(pixels, nameof(pixels));
Guard.MustBeGreaterThan(width, 0, nameof(width)); Guard.MustBeGreaterThan(width, 0, nameof(width));
@ -93,11 +88,6 @@ namespace ImageSharp
/// </summary> /// </summary>
public TColor[] PixelArray => this.pixelBuffer.Array; public TColor[] PixelArray => this.pixelBuffer.Array;
/// <summary>
/// Gets the pointer to the pixel buffer.
/// </summary>
public IntPtr DataPointer => this.pixelBuffer.Pointer;
/// <summary> /// <summary>
/// Gets the size of a single pixel in the number of bytes. /// Gets the size of a single pixel in the number of bytes.
/// </summary> /// </summary>
@ -124,7 +114,7 @@ namespace ImageSharp
public ParallelOptions ParallelOptions { get; } public ParallelOptions ParallelOptions { get; }
/// <inheritdoc /> /// <inheritdoc />
BufferSpan<TColor> IPinnedImageBuffer<TColor>.Span => this.pixelBuffer; BufferSpan<TColor> IBuffer2D<TColor>.Span => this.pixelBuffer;
private static BulkPixelOperations<TColor> Operations => BulkPixelOperations<TColor>.Instance; private static BulkPixelOperations<TColor> Operations => BulkPixelOperations<TColor>.Instance;
@ -139,15 +129,13 @@ namespace ImageSharp
get get
{ {
this.CheckCoordinates(x, y); this.CheckCoordinates(x, y);
return this.PixelArray[(y * this.Width) + x];
return Unsafe.Read<TColor>(this.pixelsBase + (((y * this.Width) + x) * Unsafe.SizeOf<TColor>()));
} }
set set
{ {
this.CheckCoordinates(x, y); this.CheckCoordinates(x, y);
this.PixelArray[(y * this.Width) + x] = value;
Unsafe.Write(this.pixelsBase + (((y * this.Width) + x) * Unsafe.SizeOf<TColor>()), value);
} }
} }
@ -179,7 +167,7 @@ namespace ImageSharp
/// </summary> /// </summary>
public void Reset() public void Reset()
{ {
Unsafe.InitBlock(this.pixelsBase, 0, (uint)(this.RowStride * this.Height)); this.pixelBuffer.Clear();
} }
/// <summary> /// <summary>
@ -251,7 +239,7 @@ namespace ImageSharp
/// <remarks>If <see cref="M:PixelAccessor.PooledMemory"/> is true then caller is responsible for ensuring <see cref="M:PixelDataPool.Return()"/> is called.</remarks> /// <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) internal TColor[] ReturnCurrentPixelsAndReplaceThemInternally(int width, int height, TColor[] pixels)
{ {
TColor[] oldPixels = this.pixelBuffer.UnPinAndTakeArrayOwnership(); TColor[] oldPixels = this.pixelBuffer.TakeArrayOwnership();
this.SetPixelBufferUnsafe(width, height, pixels); this.SetPixelBufferUnsafe(width, height, pixels);
return oldPixels; return oldPixels;
} }
@ -262,9 +250,7 @@ namespace ImageSharp
/// <param name="target">The target pixel buffer accessor.</param> /// <param name="target">The target pixel buffer accessor.</param>
internal void CopyTo(PixelAccessor<TColor> target) internal void CopyTo(PixelAccessor<TColor> target)
{ {
uint byteCount = (uint)(this.Width * this.Height * Unsafe.SizeOf<TColor>()); BufferSpan.Copy(this.pixelBuffer.Span, target.pixelBuffer.Span);
Unsafe.CopyBlock(target.pixelsBase, this.pixelsBase, byteCount);
} }
/// <summary> /// <summary>
@ -424,7 +410,7 @@ namespace ImageSharp
private void SetPixelBufferUnsafe(int width, int height, TColor[] pixels) private void SetPixelBufferUnsafe(int width, int height, TColor[] pixels)
{ {
this.SetPixelBufferUnsafe(width, height, new PinnedImageBuffer<TColor>(pixels, width, height)); this.SetPixelBufferUnsafe(width, height, new Buffer2D<TColor>(pixels, width, height));
} }
/// <summary> /// <summary>
@ -433,10 +419,9 @@ namespace ImageSharp
/// <param name="width">The width.</param> /// <param name="width">The width.</param>
/// <param name="height">The height.</param> /// <param name="height">The height.</param>
/// <param name="pixels">The pixel buffer</param> /// <param name="pixels">The pixel buffer</param>
private void SetPixelBufferUnsafe(int width, int height, PinnedImageBuffer<TColor> pixels) private void SetPixelBufferUnsafe(int width, int height, Buffer2D<TColor> pixels)
{ {
this.pixelBuffer = pixels; this.pixelBuffer = pixels;
this.pixelsBase = (byte*)pixels.Pointer;
this.Width = width; this.Width = width;
this.Height = height; this.Height = height;

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

@ -13,7 +13,7 @@ namespace ImageSharp
/// Represents an area of generic <see cref="Image{TColor}"/> pixels. /// Represents an area of generic <see cref="Image{TColor}"/> pixels.
/// </summary> /// </summary>
/// <typeparam name="TColor">The pixel format.</typeparam> /// <typeparam name="TColor">The pixel format.</typeparam>
internal sealed unsafe class PixelArea<TColor> : IDisposable internal sealed class PixelArea<TColor> : IDisposable
where TColor : struct, IPixel<TColor> where TColor : struct, IPixel<TColor>
{ {
/// <summary> /// <summary>
@ -30,7 +30,7 @@ namespace ImageSharp
/// <summary> /// <summary>
/// The underlying buffer containing the raw pixel data. /// The underlying buffer containing the raw pixel data.
/// </summary> /// </summary>
private PinnedBuffer<byte> byteBuffer; private Buffer<byte> byteBuffer;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="PixelArea{TColor}"/> class. /// Initializes a new instance of the <see cref="PixelArea{TColor}"/> class.
@ -66,8 +66,7 @@ namespace ImageSharp
this.RowStride = width * GetComponentCount(componentOrder); this.RowStride = width * GetComponentCount(componentOrder);
this.Length = bytes.Length; // TODO: Is this the right value for Length? this.Length = bytes.Length; // TODO: Is this the right value for Length?
this.byteBuffer = new PinnedBuffer<byte>(bytes); this.byteBuffer = new Buffer<byte>(bytes);
this.PixelBase = (byte*)this.byteBuffer.Pointer;
} }
/// <summary> /// <summary>
@ -117,8 +116,7 @@ namespace ImageSharp
this.RowStride = (width * GetComponentCount(componentOrder)) + padding; this.RowStride = (width * GetComponentCount(componentOrder)) + padding;
this.Length = this.RowStride * height; this.Length = this.RowStride * height;
this.byteBuffer = new PinnedBuffer<byte>(this.Length); this.byteBuffer = new Buffer<byte>(this.Length);
this.PixelBase = (byte*)this.byteBuffer.Pointer;
} }
/// <summary> /// <summary>
@ -136,21 +134,11 @@ namespace ImageSharp
/// </summary> /// </summary>
public ComponentOrder ComponentOrder { get; } public ComponentOrder ComponentOrder { get; }
/// <summary>
/// Gets the pointer to the pixel buffer.
/// </summary>
public IntPtr DataPointer => this.byteBuffer.Pointer;
/// <summary> /// <summary>
/// Gets the height. /// Gets the height.
/// </summary> /// </summary>
public int Height { get; } public int Height { get; }
/// <summary>
/// Gets the data pointer.
/// </summary>
public byte* PixelBase { get; private set; }
/// <summary> /// <summary>
/// Gets the width of one row in the number of bytes. /// Gets the width of one row in the number of bytes.
/// </summary> /// </summary>
@ -198,7 +186,7 @@ namespace ImageSharp
/// </summary> /// </summary>
public void Reset() public void Reset()
{ {
Unsafe.InitBlock(this.PixelBase, 0, (uint)(this.RowStride * this.Height)); this.byteBuffer.Clear();
} }
/// <summary> /// <summary>

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

@ -41,7 +41,7 @@ namespace ImageSharp.Processing.Processors
/// <summary> /// <summary>
/// Gets an unsafe float* pointer to the beginning of <see cref="Span"/>. /// Gets an unsafe float* pointer to the beginning of <see cref="Span"/>.
/// </summary> /// </summary>
public float* Ptr => (float*)this.Span.PointerAtOffset; public ref float Ptr => ref this.Span.DangerousGetPinnableReference();
/// <summary> /// <summary>
/// Gets the lenghth of the weights window /// Gets the lenghth of the weights window
@ -56,19 +56,18 @@ namespace ImageSharp.Processing.Processors
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vector4 ComputeWeightedRowSum(BufferSpan<Vector4> rowSpan) public Vector4 ComputeWeightedRowSum(BufferSpan<Vector4> rowSpan)
{ {
float* horizontalValues = this.Ptr; ref float horizontalValues = ref this.Ptr;
int left = this.Left; int left = this.Left;
Vector4* vecPtr = (Vector4*)rowSpan.PointerAtOffset; ref Vector4 vecPtr = ref Unsafe.Add(ref rowSpan.DangerousGetPinnableReference(), left);
vecPtr += left;
// Destination color components // Destination color components
Vector4 result = Vector4.Zero; Vector4 result = Vector4.Zero;
for (int i = 0; i < this.Length; i++) for (int i = 0; i < this.Length; i++)
{ {
float weight = horizontalValues[i]; float weight = Unsafe.Add(ref horizontalValues, i);
result += (*vecPtr) * weight; Vector4 v = Unsafe.Add(ref vecPtr, i);
vecPtr++; result += v * weight;
} }
return result; return result;
@ -83,19 +82,18 @@ namespace ImageSharp.Processing.Processors
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vector4 ComputeExpandedWeightedRowSum(BufferSpan<Vector4> rowSpan) public Vector4 ComputeExpandedWeightedRowSum(BufferSpan<Vector4> rowSpan)
{ {
float* horizontalValues = this.Ptr; ref float horizontalValues = ref this.Ptr;
int left = this.Left; int left = this.Left;
Vector4* vecPtr = (Vector4*)rowSpan.PointerAtOffset; ref Vector4 vecPtr = ref Unsafe.Add(ref rowSpan.DangerousGetPinnableReference(), left);
vecPtr += left;
// Destination color components // Destination color components
Vector4 result = Vector4.Zero; Vector4 result = Vector4.Zero;
for (int i = 0; i < this.Length; i++) for (int i = 0; i < this.Length; i++)
{ {
float weight = horizontalValues[i]; float weight = Unsafe.Add(ref horizontalValues, i);
result += (*vecPtr).Expand() * weight; Vector4 v = Unsafe.Add(ref vecPtr, i);
vecPtr++; result += v.Expand() * weight;
} }
return result; return result;
@ -109,9 +107,9 @@ namespace ImageSharp.Processing.Processors
/// <param name="x">The column position</param> /// <param name="x">The column position</param>
/// <returns>The weighted sum</returns> /// <returns>The weighted sum</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vector4 ComputeWeightedColumnSum(PinnedImageBuffer<Vector4> firstPassPixels, int x) public Vector4 ComputeWeightedColumnSum(Buffer2D<Vector4> firstPassPixels, int x)
{ {
float* verticalValues = this.Ptr; ref float verticalValues = ref this.Ptr;
int left = this.Left; int left = this.Left;
// Destination color components // Destination color components
@ -119,7 +117,7 @@ namespace ImageSharp.Processing.Processors
for (int i = 0; i < this.Length; i++) for (int i = 0; i < this.Length; i++)
{ {
float yw = verticalValues[i]; float yw = Unsafe.Add(ref verticalValues, i);
int index = left + i; int index = left + i;
result += firstPassPixels[x, index] * yw; result += firstPassPixels[x, index] * yw;
} }
@ -133,7 +131,7 @@ namespace ImageSharp.Processing.Processors
/// </summary> /// </summary>
internal class WeightsBuffer : IDisposable internal class WeightsBuffer : IDisposable
{ {
private PinnedImageBuffer<float> dataBuffer; private Buffer2D<float> dataBuffer;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="WeightsBuffer"/> class. /// Initializes a new instance of the <see cref="WeightsBuffer"/> class.
@ -142,7 +140,7 @@ namespace ImageSharp.Processing.Processors
/// <param name="destinationSize">The size of the destination window</param> /// <param name="destinationSize">The size of the destination window</param>
public WeightsBuffer(int sourceSize, int destinationSize) public WeightsBuffer(int sourceSize, int destinationSize)
{ {
this.dataBuffer = PinnedImageBuffer<float>.CreateClean(sourceSize, destinationSize); this.dataBuffer = Buffer2D<float>.CreateClean(sourceSize, destinationSize);
this.Weights = new WeightsWindow[destinationSize]; this.Weights = new WeightsWindow[destinationSize];
} }

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

@ -7,6 +7,7 @@ namespace ImageSharp.Processing.Processors
{ {
using System; using System;
using System.Buffers; using System.Buffers;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
/// <summary> /// <summary>
@ -111,13 +112,15 @@ namespace ImageSharp.Processing.Processors
WeightsWindow ws = result.GetWeightsWindow(i, left, right); WeightsWindow ws = result.GetWeightsWindow(i, left, right);
result.Weights[i] = ws; result.Weights[i] = ws;
float* weights = ws.Ptr; ref float weights = ref ws.Ptr;
for (int j = left; j <= right; j++) for (int j = left; j <= right; j++)
{ {
float weight = sampler.GetValue((j - center) / scale); float weight = sampler.GetValue((j - center) / scale);
sum += weight; sum += weight;
weights[j - left] = weight;
// weights[j - left] = weight:
Unsafe.Add(ref weights, j - left) = weight;
} }
// Normalise, best to do it here rather than in the pixel loop later on. // Normalise, best to do it here rather than in the pixel loop later on.
@ -125,7 +128,9 @@ namespace ImageSharp.Processing.Processors
{ {
for (int w = 0; w < ws.Length; w++) for (int w = 0; w < ws.Length; w++)
{ {
weights[w] = weights[w] / sum; // weights[w] = weights[w] / sum:
ref float wRef = ref Unsafe.Add(ref weights, w);
wRef = wRef / sum;
} }
} }
} }

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

@ -106,7 +106,7 @@ namespace ImageSharp.Processing.Processors
using (PixelAccessor<TColor> targetPixels = new PixelAccessor<TColor>(width, height)) using (PixelAccessor<TColor> targetPixels = new PixelAccessor<TColor>(width, height))
{ {
using (PixelAccessor<TColor> sourcePixels = source.Lock()) using (PixelAccessor<TColor> sourcePixels = source.Lock())
using (PinnedImageBuffer<Vector4> firstPassPixels = new PinnedImageBuffer<Vector4>(width, source.Height)) using (Buffer2D<Vector4> firstPassPixels = new Buffer2D<Vector4>(width, source.Height))
{ {
firstPassPixels.Clear(); firstPassPixels.Clear();
@ -117,7 +117,7 @@ namespace ImageSharp.Processing.Processors
y => y =>
{ {
// TODO: Without Parallel.For() this buffer object could be reused: // TODO: Without Parallel.For() this buffer object could be reused:
using (PinnedBuffer<Vector4> tempRowBuffer = new PinnedBuffer<Vector4>(sourcePixels.Width)) using (Buffer<Vector4> tempRowBuffer = new Buffer<Vector4>(sourcePixels.Width))
{ {
BufferSpan<TColor> sourceRow = sourcePixels.GetRowSpan(y); BufferSpan<TColor> sourceRow = sourcePixels.GetRowSpan(y);

14
tests/ImageSharp.Benchmarks/Color/Bulk/PackFromVector4ReferenceVsPointer.cs

@ -14,9 +14,9 @@
/// </summary> /// </summary>
public unsafe class PackFromVector4ReferenceVsPointer public unsafe class PackFromVector4ReferenceVsPointer
{ {
private PinnedBuffer<ImageSharp.Color> destination; private Buffer<ImageSharp.Color> destination;
private PinnedBuffer<Vector4> source; private Buffer<Vector4> source;
[Params(16, 128, 1024)] [Params(16, 128, 1024)]
public int Count { get; set; } public int Count { get; set; }
@ -24,8 +24,10 @@
[Setup] [Setup]
public void Setup() public void Setup()
{ {
this.destination = new PinnedBuffer<ImageSharp.Color>(this.Count); this.destination = new Buffer<ImageSharp.Color>(this.Count);
this.source = new PinnedBuffer<Vector4>(this.Count * 4); this.source = new Buffer<Vector4>(this.Count * 4);
this.source.Pin();
this.destination.Pin();
} }
[Cleanup] [Cleanup]
@ -38,8 +40,8 @@
[Benchmark(Baseline = true)] [Benchmark(Baseline = true)]
public void PackUsingPointers() public void PackUsingPointers()
{ {
Vector4* sp = (Vector4*)this.source.Pointer; Vector4* sp = (Vector4*)this.source.Pin();
byte* dp = (byte*)this.destination.Pointer; byte* dp = (byte*)this.destination.Pin();
int count = this.Count; int count = this.Count;
int size = sizeof(ImageSharp.Color); int size = sizeof(ImageSharp.Color);

8
tests/ImageSharp.Benchmarks/Color/Bulk/PackFromXyzw.cs

@ -8,9 +8,9 @@ namespace ImageSharp.Benchmarks.Color.Bulk
public abstract class PackFromXyzw<TColor> public abstract class PackFromXyzw<TColor>
where TColor : struct, IPixel<TColor> where TColor : struct, IPixel<TColor>
{ {
private PinnedBuffer<TColor> destination; private Buffer<TColor> destination;
private PinnedBuffer<byte> source; private Buffer<byte> source;
[Params(16, 128, 1024)] [Params(16, 128, 1024)]
public int Count { get; set; } public int Count { get; set; }
@ -18,8 +18,8 @@ namespace ImageSharp.Benchmarks.Color.Bulk
[Setup] [Setup]
public void Setup() public void Setup()
{ {
this.destination = new PinnedBuffer<TColor>(this.Count); this.destination = new Buffer<TColor>(this.Count);
this.source = new PinnedBuffer<byte>(this.Count * 4); this.source = new Buffer<byte>(this.Count * 4);
} }
[Cleanup] [Cleanup]

8
tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4.cs

@ -8,9 +8,9 @@ namespace ImageSharp.Benchmarks.Color.Bulk
public abstract class ToVector4<TColor> public abstract class ToVector4<TColor>
where TColor : struct, IPixel<TColor> where TColor : struct, IPixel<TColor>
{ {
private PinnedBuffer<TColor> source; private Buffer<TColor> source;
private PinnedBuffer<Vector4> destination; private Buffer<Vector4> destination;
[Params(64, 300, 1024)] [Params(64, 300, 1024)]
public int Count { get; set; } public int Count { get; set; }
@ -18,8 +18,8 @@ namespace ImageSharp.Benchmarks.Color.Bulk
[Setup] [Setup]
public void Setup() public void Setup()
{ {
this.source = new PinnedBuffer<TColor>(this.Count); this.source = new Buffer<TColor>(this.Count);
this.destination = new PinnedBuffer<Vector4>(this.Count); this.destination = new Buffer<Vector4>(this.Count);
} }
[Cleanup] [Cleanup]

8
tests/ImageSharp.Benchmarks/Color/Bulk/ToXyz.cs

@ -8,9 +8,9 @@ namespace ImageSharp.Benchmarks.Color.Bulk
public abstract class ToXyz<TColor> public abstract class ToXyz<TColor>
where TColor : struct, IPixel<TColor> where TColor : struct, IPixel<TColor>
{ {
private PinnedBuffer<TColor> source; private Buffer<TColor> source;
private PinnedBuffer<byte> destination; private Buffer<byte> destination;
[Params(16, 128, 1024)] [Params(16, 128, 1024)]
public int Count { get; set; } public int Count { get; set; }
@ -18,8 +18,8 @@ namespace ImageSharp.Benchmarks.Color.Bulk
[Setup] [Setup]
public void Setup() public void Setup()
{ {
this.source = new PinnedBuffer<TColor>(this.Count); this.source = new Buffer<TColor>(this.Count);
this.destination = new PinnedBuffer<byte>(this.Count * 3); this.destination = new Buffer<byte>(this.Count * 3);
} }
[Cleanup] [Cleanup]

8
tests/ImageSharp.Benchmarks/Color/Bulk/ToXyzw.cs

@ -13,9 +13,9 @@ namespace ImageSharp.Benchmarks.Color.Bulk
public abstract class ToXyzw<TColor> public abstract class ToXyzw<TColor>
where TColor : struct, IPixel<TColor> where TColor : struct, IPixel<TColor>
{ {
private PinnedBuffer<TColor> source; private Buffer<TColor> source;
private PinnedBuffer<byte> destination; private Buffer<byte> destination;
[Params(16, 128, 1024)] [Params(16, 128, 1024)]
public int Count { get; set; } public int Count { get; set; }
@ -23,8 +23,8 @@ namespace ImageSharp.Benchmarks.Color.Bulk
[Setup] [Setup]
public void Setup() public void Setup()
{ {
this.source = new PinnedBuffer<TColor>(this.Count); this.source = new Buffer<TColor>(this.Count);
this.destination = new PinnedBuffer<byte>(this.Count * 4); this.destination = new Buffer<byte>(this.Count * 4);
} }
[Cleanup] [Cleanup]

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

@ -9,7 +9,7 @@ namespace ImageSharp.Benchmarks.General
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
[Config(typeof(Config.Short))] [Config(typeof(Config.Short))]
public class ArrayCopy public class ArrayCopy
{ {
@ -58,8 +58,7 @@ namespace ImageSharp.Benchmarks.General
Buffer.MemoryCopy(pinnedSource, pinnedDestination, this.Count, this.Count); Buffer.MemoryCopy(pinnedSource, pinnedDestination, this.Count, this.Count);
} }
} }
[Benchmark(Description = "Copy using Marshal.Copy<T>")] [Benchmark(Description = "Copy using Marshal.Copy<T>")]
public unsafe void CopyUsingMarshalCopy() public unsafe void CopyUsingMarshalCopy()
{ {
@ -68,5 +67,37 @@ namespace ImageSharp.Benchmarks.General
Marshal.Copy(this.source, 0, (IntPtr)pinnedDestination, this.Count); Marshal.Copy(this.source, 0, (IntPtr)pinnedDestination, this.Count);
} }
} }
/*****************************************************************************************************************
*************** RESULTS on i7-4810MQ 2.80GHz + Clr 4.0.30319.42000, 64bit RyuJIT-v4.6.1085.0 ********************
*****************************************************************************************************************
*
* Method | Count | Mean | StdErr | StdDev | Scaled | Scaled-StdDev |
* ---------------------------------- |------ |------------ |----------- |----------- |------- |-------------- |
* 'Copy using Array.Copy()' | 10 | 20.3074 ns | 0.1194 ns | 0.2068 ns | 1.00 | 0.00 |
* 'Copy using Unsafe<T>' | 10 | 6.1002 ns | 0.1981 ns | 0.3432 ns | 0.30 | 0.01 |
* 'Copy using Buffer.BlockCopy()' | 10 | 10.7879 ns | 0.0984 ns | 0.1705 ns | 0.53 | 0.01 |
* 'Copy using Buffer.MemoryCopy<T>' | 10 | 4.9625 ns | 0.0200 ns | 0.0347 ns | 0.24 | 0.00 |
* 'Copy using Marshal.Copy<T>' | 10 | 16.1782 ns | 0.0919 ns | 0.1592 ns | 0.80 | 0.01 |
*
* 'Copy using Array.Copy()' | 100 | 31.5945 ns | 0.2908 ns | 0.5037 ns | 1.00 | 0.00 |
* 'Copy using Unsafe<T>' | 100 | 10.2722 ns | 0.5202 ns | 0.9010 ns | 0.33 | 0.02 |
* 'Copy using Buffer.BlockCopy()' | 100 | 22.0322 ns | 0.0284 ns | 0.0493 ns | 0.70 | 0.01 |
* 'Copy using Buffer.MemoryCopy<T>' | 100 | 10.2472 ns | 0.0359 ns | 0.0622 ns | 0.32 | 0.00 |
* 'Copy using Marshal.Copy<T>' | 100 | 34.3820 ns | 1.1868 ns | 2.0555 ns | 1.09 | 0.05 |
*
* 'Copy using Array.Copy()' | 1000 | 40.9743 ns | 0.0521 ns | 0.0902 ns | 1.00 | 0.00 |
* 'Copy using Unsafe<T>' | 1000 | 42.7840 ns | 2.0139 ns | 3.4882 ns | 1.04 | 0.07 |
* 'Copy using Buffer.BlockCopy()' | 1000 | 33.7361 ns | 0.0751 ns | 0.1300 ns | 0.82 | 0.00 |
* 'Copy using Buffer.MemoryCopy<T>' | 1000 | 35.7541 ns | 0.0480 ns | 0.0832 ns | 0.87 | 0.00 |
* 'Copy using Marshal.Copy<T>' | 1000 | 42.2028 ns | 0.2769 ns | 0.4795 ns | 1.03 | 0.01 |
*
* 'Copy using Array.Copy()' | 10000 | 200.0438 ns | 0.2251 ns | 0.3899 ns | 1.00 | 0.00 |
* 'Copy using Unsafe<T>' | 10000 | 389.6957 ns | 13.2770 ns | 22.9964 ns | 1.95 | 0.09 |
* 'Copy using Buffer.BlockCopy()' | 10000 | 191.3478 ns | 0.1557 ns | 0.2697 ns | 0.96 | 0.00 |
* 'Copy using Buffer.MemoryCopy<T>' | 10000 | 196.4679 ns | 0.2731 ns | 0.4730 ns | 0.98 | 0.00 |
* 'Copy using Marshal.Copy<T>' | 10000 | 202.5392 ns | 0.5561 ns | 0.9631 ns | 1.01 | 0.00 |
*
*/
} }
} }

6
tests/ImageSharp.Benchmarks/General/ClearBuffer.cs

@ -11,7 +11,7 @@ namespace ImageSharp.Benchmarks.General
public unsafe class ClearBuffer public unsafe class ClearBuffer
{ {
private PinnedBuffer<Color> buffer; private Buffer<Color> buffer;
[Params(32, 128, 512)] [Params(32, 128, 512)]
public int Count { get; set; } public int Count { get; set; }
@ -19,7 +19,7 @@ namespace ImageSharp.Benchmarks.General
[Setup] [Setup]
public void Setup() public void Setup()
{ {
this.buffer = new PinnedBuffer<ImageSharp.Color>(this.Count); this.buffer = new Buffer<ImageSharp.Color>(this.Count);
} }
[Cleanup] [Cleanup]
@ -37,7 +37,7 @@ namespace ImageSharp.Benchmarks.General
[Benchmark] [Benchmark]
public void Unsafe_InitBlock() public void Unsafe_InitBlock()
{ {
Unsafe.InitBlock((void*)this.buffer.Pointer, default(byte), (uint)this.Count*sizeof(uint)); Unsafe.InitBlock((void*)this.buffer.Pin(), default(byte), (uint)this.Count*sizeof(uint));
} }
} }
} }

7
tests/ImageSharp.Benchmarks/General/IterateArray.cs

@ -8,7 +8,7 @@ namespace ImageSharp.Benchmarks.General
public class IterateArray public class IterateArray
{ {
// Usual pinned stuff // Usual pinned stuff
private PinnedBuffer<Vector4> buffer; private Buffer<Vector4> buffer;
// An array that's not pinned by intent! // An array that's not pinned by intent!
private Vector4[] array; private Vector4[] array;
@ -19,7 +19,8 @@ namespace ImageSharp.Benchmarks.General
[Setup] [Setup]
public void Setup() public void Setup()
{ {
this.buffer = new PinnedBuffer<Vector4>(this.Length); this.buffer = new Buffer<Vector4>(this.Length);
this.buffer.Pin();
this.array = new Vector4[this.Length]; this.array = new Vector4[this.Length];
} }
@ -41,7 +42,7 @@ namespace ImageSharp.Benchmarks.General
{ {
Vector4 sum = new Vector4(); Vector4 sum = new Vector4();
Vector4* ptr = (Vector4*) this.buffer.Pointer; Vector4* ptr = (Vector4*) this.buffer.Pin();
Vector4* end = ptr + this.Length; Vector4* end = ptr + this.Length;
for (; ptr < end; ptr++) for (; ptr < end; ptr++)

360
tests/ImageSharp.Benchmarks/General/PixelIndexing.cs

@ -0,0 +1,360 @@
namespace ImageSharp.Benchmarks.General
{
using System.Numerics;
using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes;
// Pixel indexing benchmarks compare different methods for getting/setting all pixel values in a subsegment of a single pixel row.
public abstract unsafe class PixelIndexing
{
/// <summary>
/// https://github.com/dotnet/corefx/blob/master/src/System.Memory/src/System/Pinnable.cs
/// </summary>
protected class Pinnable<T>
{
public T Data;
}
/// <summary>
/// The indexer methods are encapsulated into a struct to make sure everything is inlined.
/// </summary>
internal struct Data
{
private Vector4* pointer;
private Pinnable<Vector4> pinnable;
private Vector4[] array;
private int width;
public Data(Buffer2D<Vector4> buffer)
{
this.pointer = (Vector4*)buffer.Pin();
this.pinnable = Unsafe.As<Pinnable<Vector4>>(buffer.Array);
this.array = buffer.Array;
this.width = buffer.Width;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vector4 GetPointersBasicImpl(int x, int y)
{
return this.pointer[y * this.width + x];
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vector4 GetPointersSrcsUnsafeImpl(int x, int y)
{
// This is the original solution in PixelAccessor:
return Unsafe.Read<Vector4>((byte*)this.pointer + (((y * this.width) + x) * Unsafe.SizeOf<Vector4>()));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vector4 GetReferencesImpl(int x, int y)
{
int elementOffset = (y * this.width) + x;
return Unsafe.Add(ref this.pinnable.Data, elementOffset);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ref Vector4 GetReferencesRefReturnsImpl(int x, int y)
{
int elementOffset = (y * this.width) + x;
return ref Unsafe.Add(ref this.pinnable.Data, elementOffset);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void IndexWithPointersBasicImpl(int x, int y, Vector4 v)
{
this.pointer[y * this.width + x] = v;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void IndexWithPointersSrcsUnsafeImpl(int x, int y, Vector4 v)
{
Unsafe.Write((byte*)this.pointer + (((y * this.width) + x) * Unsafe.SizeOf<Vector4>()), v);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void IndexWithReferencesOnPinnableIncorrectImpl(int x, int y, Vector4 v)
{
int elementOffset = (y * this.width) + x;
// Incorrect, because also should add a runtime-specific byte offset here. See https://github.com/dotnet/corefx/blob/master/src/System.Memory/src/System/Span.cs#L68
Unsafe.Add(ref this.pinnable.Data, elementOffset) = v;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ref Vector4 IndexWithReferencesOnPinnableIncorrectRefReturnImpl(int x, int y)
{
int elementOffset = (y * this.width) + x;
// Incorrect, because also should add a runtime-specific byte offset here. See https://github.com/dotnet/corefx/blob/master/src/System.Memory/src/System/Span.cs#L68
return ref Unsafe.Add(ref this.pinnable.Data, elementOffset);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void IndexWithUnsafeReferenceArithmeticsOnArray0Impl(int x, int y, Vector4 v)
{
int elementOffset = (y * this.width) + x;
Unsafe.Add(ref this.array[0], elementOffset) = v;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ref Vector4 IndexWithUnsafeReferenceArithmeticsOnArray0RefReturnImpl(int x, int y)
{
int elementOffset = (y * this.width) + x;
return ref Unsafe.Add(ref this.array[0], elementOffset);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void IndexSetArrayStraightforward(int x, int y, Vector4 v)
{
// No magic.
// We just index right into the array as normal people do.
// And it looks like this is the fastest way!
this.array[(y * this.width) + x] = v;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ref Vector4 IndexWithReferencesOnArrayStraightforwardRefReturnImpl(int x, int y)
{
// No magic.
// We just index right into the array as normal people do.
// And it looks like this is the fastest way!
return ref this.array[(y * this.width) + x];
}
}
internal Buffer2D<Vector4> buffer;
protected int width;
protected int startIndex;
protected int endIndex;
protected Vector4* pointer;
protected Vector4[] array;
protected Pinnable<Vector4> pinnable;
// [Params(1024)]
public int Count { get; set; } = 1024;
[Setup]
public void Setup()
{
this.width = 2048;
this.buffer = new Buffer2D<Vector4>(2048, 2048);
this.pointer = (Vector4*)this.buffer.Pin();
this.array = this.buffer.Array;
this.pinnable = Unsafe.As<Pinnable<Vector4>>(this.array);
this.startIndex = 2048 / 2 - (this.Count / 2);
this.endIndex = 2048 / 2 + (this.Count / 2);
}
[Cleanup]
public void Cleanup()
{
this.buffer.Dispose();
}
}
public class PixelIndexingGetter : PixelIndexing
{
[Benchmark(Description = "Index.Get: Pointers+arithmetics", Baseline = true)]
public Vector4 IndexWithPointersBasic()
{
Vector4 sum = Vector4.Zero;
Data data = new Data(this.buffer);
int y = this.startIndex;
for (int x = this.startIndex; x < this.endIndex; x++)
{
sum += data.GetPointersBasicImpl(x, y);
}
return sum;
}
[Benchmark(Description = "Index.Get: Pointers+SRCS.Unsafe")]
public Vector4 IndexWithPointersSrcsUnsafe()
{
Vector4 sum = Vector4.Zero;
Data data = new Data(this.buffer);
int y = this.startIndex;
for (int x = this.startIndex; x < this.endIndex; x++)
{
sum += data.GetPointersSrcsUnsafeImpl(x, y);
}
return sum;
}
[Benchmark(Description = "Index.Get: References")]
public Vector4 IndexWithReferences()
{
Vector4 sum = Vector4.Zero;
Data data = new Data(this.buffer);
int y = this.startIndex;
for (int x = this.startIndex; x < this.endIndex; x++)
{
sum += data.GetReferencesImpl(x, y);
}
return sum;
}
[Benchmark(Description = "Index.Get: References|refreturns")]
public Vector4 IndexWithReferencesRefReturns()
{
Vector4 sum = Vector4.Zero;
Data data = new Data(this.buffer);
int y = this.startIndex;
for (int x = this.startIndex; x < this.endIndex; x++)
{
sum += data.GetReferencesRefReturnsImpl(x, y);
}
return sum;
}
}
public class PixelIndexingSetter : PixelIndexing
{
[Benchmark(Description = "!!! Index.Set: Pointers|arithmetics", Baseline = true)]
public void IndexWithPointersBasic()
{
Vector4 v = new Vector4(1, 2, 3, 4);
Data data = new Data(this.buffer);
int y = this.startIndex;
for (int x = this.startIndex; x < this.endIndex; x++)
{
data.IndexWithPointersBasicImpl(x, y, v);
}
}
[Benchmark(Description = "Index.Set: Pointers|SRCS.Unsafe")]
public void IndexWithPointersSrcsUnsafe()
{
Vector4 v = new Vector4(1, 2, 3, 4);
Data data = new Data(this.buffer);
int y = this.startIndex;
for (int x = this.startIndex; x < this.endIndex; x++)
{
data.IndexWithPointersSrcsUnsafeImpl(x, y, v);
}
}
[Benchmark(Description = "Index.Set: References|IncorrectPinnable")]
public void IndexWithReferencesPinnableBasic()
{
Vector4 v = new Vector4(1, 2, 3, 4);
Data data = new Data(this.buffer);
int y = this.startIndex;
for (int x = this.startIndex; x < this.endIndex; x++)
{
data.IndexWithReferencesOnPinnableIncorrectImpl(x, y, v);
}
}
[Benchmark(Description = "Index.Set: References|IncorrectPinnable|refreturn")]
public void IndexWithReferencesPinnableRefReturn()
{
Vector4 v = new Vector4(1, 2, 3, 4);
Data data = new Data(this.buffer);
int y = this.startIndex;
for (int x = this.startIndex; x < this.endIndex; x++)
{
data.IndexWithReferencesOnPinnableIncorrectRefReturnImpl(x, y) = v;
}
}
[Benchmark(Description = "Index.Set: References|Array[0]Unsafe")]
public void IndexWithReferencesArrayBasic()
{
Vector4 v = new Vector4(1, 2, 3, 4);
Data data = new Data(this.buffer);
int y = this.startIndex;
for (int x = this.startIndex; x < this.endIndex; x++)
{
data.IndexWithUnsafeReferenceArithmeticsOnArray0Impl(x, y, v);
}
}
[Benchmark(Description = "Index.Set: References|Array[0]Unsafe|refreturn")]
public void IndexWithReferencesArrayRefReturn()
{
Vector4 v = new Vector4(1, 2, 3, 4);
Data data = new Data(this.buffer);
int y = this.startIndex;
for (int x = this.startIndex; x < this.endIndex; x++)
{
data.IndexWithUnsafeReferenceArithmeticsOnArray0RefReturnImpl(x, y) = v;
}
}
[Benchmark(Description = "!!! Index.Set: References|Array+Straight")]
public void IndexWithReferencesArrayStraightforward()
{
Vector4 v = new Vector4(1, 2, 3, 4);
Data data = new Data(this.buffer);
int y = this.startIndex;
for (int x = this.startIndex; x < this.endIndex; x++)
{
// No magic.
// We just index right into the array as normal people do.
// And it looks like this is the fastest way!
data.IndexSetArrayStraightforward(x, y, v);
}
}
[Benchmark(Description = "!!! Index.Set: References|Array+Straight|refreturn")]
public void IndexWithReferencesArrayStraightforwardRefReturn()
{
Vector4 v = new Vector4(1, 2, 3, 4);
Data data = new Data(this.buffer);
int y = this.startIndex;
for (int x = this.startIndex; x < this.endIndex; x++)
{
// No magic.
// We just index right into the array as normal people do.
// And it looks like this is the fastest way!
data.IndexWithReferencesOnArrayStraightforwardRefReturnImpl(x, y) = v;
}
}
[Benchmark(Description = "!!! Index.Set: SmartUnsafe")]
public void SmartUnsafe()
{
Vector4 v = new Vector4(1, 2, 3, 4);
Data data = new Data(this.buffer);
// This method is basically an unsafe variant of .GetRowSpan(y) + indexing individual pixels in the row.
// If a user seriously needs by-pixel manipulation to be performant, we should provide this option.
ref Vector4 rowStart = ref data.IndexWithReferencesOnArrayStraightforwardRefReturnImpl(this.startIndex, this.startIndex);
for (int i = 0; i < this.Count; i++)
{
// We don't have to add 'Width * y' here!
Unsafe.Add(ref rowStart, i) = v;
}
}
}
}

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

@ -18,7 +18,7 @@ namespace ImageSharp.Tests.Colors
} }
// For 4.6 test runner MemberData does not work without redeclaring the public field in the derived test class: // 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 static new TheoryData<int> ArraySizesData => new TheoryData<int> { 7, 16, 1111 };
[Fact] [Fact]
public void IsSpecialImplementation() public void IsSpecialImplementation()
@ -45,8 +45,8 @@ namespace ImageSharp.Tests.Colors
int times = 200000; int times = 200000;
int count = 1024; int count = 1024;
using (PinnedBuffer<ImageSharp.Color> source = new PinnedBuffer<ImageSharp.Color>(count)) using (Buffer<ImageSharp.Color> source = new Buffer<ImageSharp.Color>(count))
using (PinnedBuffer<Vector4> dest = new PinnedBuffer<Vector4>(count)) using (Buffer<Vector4> dest = new Buffer<Vector4>(count))
{ {
this.Measure( this.Measure(
times, times,
@ -66,7 +66,7 @@ namespace ImageSharp.Tests.Colors
{ {
} }
public static new TheoryData<int> ArraySizesData => new TheoryData<int> { 7, 16, 1111 }; //public static new TheoryData<int> ArraySizesData => new TheoryData<int> { 7, 16, 1111 };
} }
[Theory] [Theory]
@ -310,18 +310,18 @@ namespace ImageSharp.Tests.Colors
where TSource : struct where TSource : struct
where TDest : struct where TDest : struct
{ {
public PinnedBuffer<TSource> SourceBuffer { get; } public Buffer<TSource> SourceBuffer { get; }
public PinnedBuffer<TDest> ActualDestBuffer { get; } public Buffer<TDest> ActualDestBuffer { get; }
public PinnedBuffer<TDest> ExpectedDestBuffer { get; } public Buffer<TDest> ExpectedDestBuffer { get; }
public BufferSpan<TSource> Source => this.SourceBuffer; public BufferSpan<TSource> Source => this.SourceBuffer;
public BufferSpan<TDest> ActualDest => this.ActualDestBuffer; public BufferSpan<TDest> ActualDest => this.ActualDestBuffer;
public TestBuffers(TSource[] source, TDest[] expectedDest) public TestBuffers(TSource[] source, TDest[] expectedDest)
{ {
this.SourceBuffer = new PinnedBuffer<TSource>(source); this.SourceBuffer = new Buffer<TSource>(source);
this.ExpectedDestBuffer = new PinnedBuffer<TDest>(expectedDest); this.ExpectedDestBuffer = new Buffer<TDest>(expectedDest);
this.ActualDestBuffer = new PinnedBuffer<TDest>(expectedDest.Length); this.ActualDestBuffer = new Buffer<TDest>(expectedDest.Length);
} }
public void Dispose() public void Dispose()

32
tests/ImageSharp.Tests/Common/PinnedImageBufferTests.cs → tests/ImageSharp.Tests/Common/Buffer2DTests.cs

@ -1,20 +1,34 @@
// ReSharper disable InconsistentNaming // ReSharper disable InconsistentNaming
namespace ImageSharp.Tests.Common namespace ImageSharp.Tests.Common
{ {
using System;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using Xunit; using Xunit;
using static TestStructs; using static TestStructs;
public unsafe class PinnedImageBufferTests public unsafe class Buffer2DTests
{ {
// ReSharper disable once ClassNeverInstantiated.Local
private class Assert : Xunit.Assert
{
public static void SpanPointsTo<T>(BufferSpan<T> span, Buffer<T> buffer, int bufferOffset = 0)
where T : struct
{
ref T actual = ref span.DangerousGetPinnableReference();
ref T expected = ref Unsafe.Add(ref buffer[0], bufferOffset);
Assert.True(Unsafe.AreSame(ref expected, ref actual), "span does not point to the expected position");
}
}
[Theory] [Theory]
[InlineData(7, 42)] [InlineData(7, 42)]
[InlineData(1025, 17)] [InlineData(1025, 17)]
public void Construct(int width, int height) public void Construct(int width, int height)
{ {
using (PinnedImageBuffer<Foo> buffer = new PinnedImageBuffer<Foo>(width, height)) using (Buffer2D<Foo> buffer = new Buffer2D<Foo>(width, height))
{ {
Assert.Equal(width, buffer.Width); Assert.Equal(width, buffer.Width);
Assert.Equal(height, buffer.Height); Assert.Equal(height, buffer.Height);
@ -28,7 +42,7 @@ namespace ImageSharp.Tests.Common
public void Construct_FromExternalArray(int width, int height) public void Construct_FromExternalArray(int width, int height)
{ {
Foo[] array = new Foo[width * height + 10]; Foo[] array = new Foo[width * height + 10];
using (PinnedImageBuffer<Foo> buffer = new PinnedImageBuffer<Foo>(array, width, height)) using (Buffer2D<Foo> buffer = new Buffer2D<Foo>(array, width, height))
{ {
Assert.Equal(width, buffer.Width); Assert.Equal(width, buffer.Width);
Assert.Equal(height, buffer.Height); Assert.Equal(height, buffer.Height);
@ -42,7 +56,7 @@ namespace ImageSharp.Tests.Common
{ {
for (int i = 0; i < 100; i++) for (int i = 0; i < 100; i++)
{ {
using (PinnedImageBuffer<int> buffer = PinnedImageBuffer<int>.CreateClean(42, 42)) using (Buffer2D<int> buffer = Buffer2D<int>.CreateClean(42, 42))
{ {
for (int j = 0; j < buffer.Length; j++) for (int j = 0; j < buffer.Length; j++)
{ {
@ -59,13 +73,13 @@ namespace ImageSharp.Tests.Common
[InlineData(17, 42, 41)] [InlineData(17, 42, 41)]
public void GetRowSpanY(int width, int height, int y) public void GetRowSpanY(int width, int height, int y)
{ {
using (PinnedImageBuffer<Foo> buffer = new PinnedImageBuffer<Foo>(width, height)) using (Buffer2D<Foo> buffer = new Buffer2D<Foo>(width, height))
{ {
BufferSpan<Foo> span = buffer.GetRowSpan(y); BufferSpan<Foo> span = buffer.GetRowSpan(y);
Assert.Equal(width * y, span.Start); Assert.Equal(width * y, span.Start);
Assert.Equal(width, span.Length); Assert.Equal(width, span.Length);
Assert.Equal(buffer.Pointer + sizeof(Foo) * width * y, span.PointerAtOffset); Assert.SpanPointsTo(span, buffer, width * y);
} }
} }
@ -75,13 +89,13 @@ namespace ImageSharp.Tests.Common
[InlineData(17, 42, 0, 41)] [InlineData(17, 42, 0, 41)]
public void GetRowSpanXY(int width, int height, int x, int y) public void GetRowSpanXY(int width, int height, int x, int y)
{ {
using (PinnedImageBuffer<Foo> buffer = new PinnedImageBuffer<Foo>(width, height)) using (Buffer2D<Foo> buffer = new Buffer2D<Foo>(width, height))
{ {
BufferSpan<Foo> span = buffer.GetRowSpan(x, y); BufferSpan<Foo> span = buffer.GetRowSpan(x, y);
Assert.Equal(width * y + x, span.Start); Assert.Equal(width * y + x, span.Start);
Assert.Equal(width - x, span.Length); Assert.Equal(width - x, span.Length);
Assert.Equal(buffer.Pointer + sizeof(Foo) * (width * y + x), span.PointerAtOffset); Assert.SpanPointsTo(span, buffer, width * y + x);
} }
} }
@ -91,7 +105,7 @@ namespace ImageSharp.Tests.Common
[InlineData(99, 88, 98, 87)] [InlineData(99, 88, 98, 87)]
public void Indexer(int width, int height, int x, int y) public void Indexer(int width, int height, int x, int y)
{ {
using (PinnedImageBuffer<Foo> buffer = new PinnedImageBuffer<Foo>(width, height)) using (Buffer2D<Foo> buffer = new Buffer2D<Foo>(width, height))
{ {
Foo[] array = buffer.Array; Foo[] array = buffer.Array;

275
tests/ImageSharp.Tests/Common/BufferSpanTests.cs

@ -1,5 +1,6 @@
// ReSharper disable ObjectCreationAsStatement // ReSharper disable ObjectCreationAsStatement
// ReSharper disable InconsistentNaming // ReSharper disable InconsistentNaming
namespace ImageSharp.Tests.Common namespace ImageSharp.Tests.Common
{ {
using System; using System;
@ -8,40 +9,50 @@ namespace ImageSharp.Tests.Common
using Xunit; using Xunit;
using static TestStructs; using static TestStructs;
public unsafe class BufferSpanTests public unsafe class BufferSpanTests
{ {
// ReSharper disable once ClassNeverInstantiated.Local
private class Assert : Xunit.Assert
{
public static void SameRefs<T1, T2>(ref T1 a, ref T2 b)
{
ref T1 bb = ref Unsafe.As<T2, T1>(ref b);
True(Unsafe.AreSame(ref a, ref bb), "References are not same!");
}
}
[Fact] [Fact]
public void AsBytes() public void AsBytes()
{ {
Foo[] fooz = { new Foo(1, 2), new Foo(3, 4), new Foo(5, 6) }; Foo[] fooz = { new Foo(1, 2), new Foo(3, 4), new Foo(5, 6) };
using (PinnedBuffer<Foo> colorBuf = new PinnedBuffer<Foo>(fooz)) using (Buffer<Foo> colorBuf = new Buffer<Foo>(fooz))
{ {
BufferSpan<Foo> orig = colorBuf.Slice(1); BufferSpan<Foo> orig = colorBuf.Slice(1);
BufferSpan<byte> asBytes = (BufferSpan < byte > )orig; BufferSpan<byte> asBytes = orig.AsBytes();
Assert.Equal(asBytes.Start, sizeof(Foo)); Assert.Equal(asBytes.Start, sizeof(Foo));
Assert.Equal(orig.PointerAtOffset, asBytes.PointerAtOffset); Assert.Equal(orig.Length * Unsafe.SizeOf<Foo>(), asBytes.Length);
Assert.SameRefs(ref orig.DangerousGetPinnableReference(), ref asBytes.DangerousGetPinnableReference());
} }
} }
public class Construct public class Construct
{ {
[Fact] [Fact]
public void Basic() public void Basic()
{ {
Foo[] array = Foo.CreateArray(3); Foo[] array = Foo.CreateArray(3);
fixed (Foo* p = array)
{
// Act:
BufferSpan<Foo> span = new BufferSpan<Foo>(array, p);
// Assert: // Act:
Assert.Equal(array, span.Array); BufferSpan<Foo> span = new BufferSpan<Foo>(array);
Assert.Equal((IntPtr)p, span.PointerAtOffset);
Assert.Equal(3, span.Length); // Assert:
} Assert.Equal(array, span.Array);
Assert.Equal(3, span.Length);
Assert.SameRefs(ref array[0], ref span.DangerousGetPinnableReference());
} }
[Fact] [Fact]
@ -49,17 +60,15 @@ namespace ImageSharp.Tests.Common
{ {
Foo[] array = Foo.CreateArray(4); Foo[] array = Foo.CreateArray(4);
int start = 2; int start = 2;
fixed (Foo* p = array)
{ // Act:
// Act: BufferSpan<Foo> span = new BufferSpan<Foo>(array, start);
BufferSpan<Foo> span = new BufferSpan<Foo>(array, p, start);
// Assert:
// Assert: Assert.Equal(array, span.Array);
Assert.Equal(array, span.Array); Assert.Equal(start, span.Start);
Assert.Equal(start, span.Start); Assert.SameRefs(ref array[start], ref span.DangerousGetPinnableReference());
Assert.Equal((IntPtr)(p + start), span.PointerAtOffset); Assert.Equal(array.Length - start, span.Length);
Assert.Equal(array.Length - start, span.Length);
}
} }
[Fact] [Fact]
@ -68,17 +77,14 @@ namespace ImageSharp.Tests.Common
Foo[] array = Foo.CreateArray(10); Foo[] array = Foo.CreateArray(10);
int start = 2; int start = 2;
int length = 3; int length = 3;
fixed (Foo* p = array) // Act:
{ BufferSpan<Foo> span = new BufferSpan<Foo>(array, start, length);
// Act:
BufferSpan<Foo> span = new BufferSpan<Foo>(array, p, start, length); // Assert:
Assert.Equal(array, span.Array);
// Assert: Assert.Equal(start, span.Start);
Assert.Equal(array, span.Array); Assert.SameRefs(ref array[start], ref span.DangerousGetPinnableReference());
Assert.Equal(start, span.Start); Assert.Equal(length, span.Length);
Assert.Equal((IntPtr)(p + start), span.PointerAtOffset);
Assert.Equal(length, span.Length);
}
} }
} }
@ -92,19 +98,16 @@ namespace ImageSharp.Tests.Common
int start1 = 2; int start1 = 2;
int totalOffset = start0 + start1; int totalOffset = start0 + start1;
fixed (Foo* p = array) BufferSpan<Foo> span = new BufferSpan<Foo>(array, start0);
{
BufferSpan<Foo> span = new BufferSpan<Foo>(array, p, start0);
// Act: // Act:
span = span.Slice(start1); span = span.Slice(start1);
// Assert: // Assert:
Assert.Equal(array, span.Array); Assert.Equal(array, span.Array);
Assert.Equal(totalOffset, span.Start); Assert.Equal(totalOffset, span.Start);
Assert.Equal((IntPtr)(p + totalOffset), span.PointerAtOffset); Assert.SameRefs(ref array[totalOffset], ref span.DangerousGetPinnableReference());
Assert.Equal(array.Length - totalOffset, span.Length); Assert.Equal(array.Length - totalOffset, span.Length);
}
} }
[Fact] [Fact]
@ -116,24 +119,19 @@ namespace ImageSharp.Tests.Common
int totalOffset = start0 + start1; int totalOffset = start0 + start1;
int sliceLength = 3; int sliceLength = 3;
fixed (Foo* p = array) BufferSpan<Foo> span = new BufferSpan<Foo>(array, start0);
{
BufferSpan<Foo> span = new BufferSpan<Foo>(array, p, start0);
// Act: // Act:
span = span.Slice(start1, sliceLength); span = span.Slice(start1, sliceLength);
// Assert: // Assert:
Assert.Equal(array, span.Array); Assert.Equal(array, span.Array);
Assert.Equal(totalOffset, span.Start); Assert.Equal(totalOffset, span.Start);
Assert.Equal((IntPtr)(p + totalOffset), span.PointerAtOffset); Assert.SameRefs(ref array[totalOffset], ref span.DangerousGetPinnableReference());
Assert.Equal(sliceLength, span.Length); Assert.Equal(sliceLength, span.Length);
}
} }
} }
[Theory] [Theory]
[InlineData(4)] [InlineData(4)]
[InlineData(1500)] [InlineData(1500)]
@ -142,21 +140,17 @@ namespace ImageSharp.Tests.Common
Foo[] array = Foo.CreateArray(count + 42); Foo[] array = Foo.CreateArray(count + 42);
int offset = 2; int offset = 2;
fixed (Foo* p = array) BufferSpan<Foo> ap = new BufferSpan<Foo>(array, offset);
{
BufferSpan<Foo> ap = new BufferSpan<Foo>(array, p, offset);
// Act: // Act:
ap.Clear(count); ap.Clear(count);
Assert.NotEqual(default(Foo), array[offset-1]); Assert.NotEqual(default(Foo), array[offset - 1]);
Assert.Equal(default(Foo), array[offset]); Assert.Equal(default(Foo), array[offset]);
Assert.Equal(default(Foo), array[offset + count-1]); Assert.Equal(default(Foo), array[offset + count - 1]);
Assert.NotEqual(default(Foo), array[offset + count]); Assert.NotEqual(default(Foo), array[offset + count]);
}
} }
public class Indexer public class Indexer
{ {
public static readonly TheoryData<int, int, int> IndexerData = public static readonly TheoryData<int, int, int> IndexerData =
@ -175,14 +169,11 @@ namespace ImageSharp.Tests.Common
public void Read(int length, int start, int index) public void Read(int length, int start, int index)
{ {
Foo[] a = Foo.CreateArray(length); Foo[] a = Foo.CreateArray(length);
fixed (Foo* p = a) BufferSpan<Foo> span = new BufferSpan<Foo>(a, start);
{
BufferSpan<Foo> span = new BufferSpan<Foo>(a, p, start);
Foo element = span[index]; Foo element = span[index];
Assert.Equal(a[start + index], element); Assert.Equal(a[start + index], element);
}
} }
[Theory] [Theory]
@ -190,17 +181,46 @@ namespace ImageSharp.Tests.Common
public void Write(int length, int start, int index) public void Write(int length, int start, int index)
{ {
Foo[] a = Foo.CreateArray(length); Foo[] a = Foo.CreateArray(length);
fixed (Foo* p = a) BufferSpan<Foo> span = new BufferSpan<Foo>(a, start);
{
BufferSpan<Foo> span = new BufferSpan<Foo>(a, p, start);
span[index] = new Foo(666, 666); span[index] = new Foo(666, 666);
Assert.Equal(new Foo(666, 666), a[start + index]); Assert.Equal(new Foo(666, 666), a[start + index]);
} }
[Theory]
[InlineData(10, 0, 0, 5)]
[InlineData(10, 1, 1, 5)]
[InlineData(10, 1, 1, 6)]
[InlineData(10, 1, 1, 7)]
public void AsBytes_Read(int length, int start, int index, int byteOffset)
{
Foo[] a = Foo.CreateArray(length);
BufferSpan<Foo> span = new BufferSpan<Foo>(a, start);
BufferSpan<byte> bytes = span.AsBytes();
byte actual = bytes[index * Unsafe.SizeOf<Foo>() + byteOffset];
ref byte baseRef = ref Unsafe.As<Foo, byte>(ref a[0]);
byte expected = Unsafe.Add(ref baseRef, (start + index) * Unsafe.SizeOf<Foo>() + byteOffset);
Assert.Equal(expected, actual);
} }
} }
[Theory]
[InlineData(0, 4)]
[InlineData(2, 4)]
[InlineData(3, 4)]
public void DangerousGetPinnableReference(int start, int length)
{
Foo[] a = Foo.CreateArray(length);
BufferSpan<Foo> span = new BufferSpan<Foo>(a, start);
ref Foo r = ref span.DangerousGetPinnableReference();
Assert.True(Unsafe.AreSame(ref a[start], ref r));
}
public class Copy public class Copy
{ {
@ -238,14 +258,10 @@ namespace ImageSharp.Tests.Common
Foo[] source = Foo.CreateArray(count + 2); Foo[] source = Foo.CreateArray(count + 2);
Foo[] dest = new Foo[count + 5]; Foo[] dest = new Foo[count + 5];
fixed (Foo* pSource = source) BufferSpan<Foo> apSource = new BufferSpan<Foo>(source, 1);
fixed (Foo* pDest = dest) BufferSpan<Foo> apDest = new BufferSpan<Foo>(dest, 1);
{
BufferSpan<Foo> apSource = new BufferSpan<Foo>(source, pSource, 1);
BufferSpan<Foo> apDest = new BufferSpan<Foo>(dest, pDest, 1);
BufferSpan.Copy(apSource, apDest, count-1); BufferSpan.Copy(apSource, apDest, count - 1);
}
AssertNotDefault(source, 1); AssertNotDefault(source, 1);
AssertNotDefault(dest, 1); AssertNotDefault(dest, 1);
@ -253,7 +269,7 @@ namespace ImageSharp.Tests.Common
Assert.NotEqual(source[0], dest[0]); Assert.NotEqual(source[0], dest[0]);
Assert.Equal(source[1], dest[1]); Assert.Equal(source[1], dest[1]);
Assert.Equal(source[2], dest[2]); Assert.Equal(source[2], dest[2]);
Assert.Equal(source[count-1], dest[count-1]); Assert.Equal(source[count - 1], dest[count - 1]);
Assert.NotEqual(source[count], dest[count]); Assert.NotEqual(source[count], dest[count]);
} }
@ -265,14 +281,10 @@ namespace ImageSharp.Tests.Common
AlignedFoo[] source = AlignedFoo.CreateArray(count + 2); AlignedFoo[] source = AlignedFoo.CreateArray(count + 2);
AlignedFoo[] dest = new AlignedFoo[count + 5]; AlignedFoo[] dest = new AlignedFoo[count + 5];
fixed (AlignedFoo* pSource = source) BufferSpan<AlignedFoo> apSource = new BufferSpan<AlignedFoo>(source, 1);
fixed (AlignedFoo* pDest = dest) BufferSpan<AlignedFoo> apDest = new BufferSpan<AlignedFoo>(dest, 1);
{
BufferSpan<AlignedFoo> apSource = new BufferSpan<AlignedFoo>(source, pSource, 1);
BufferSpan<AlignedFoo> apDest = new BufferSpan<AlignedFoo>(dest, pDest, 1);
BufferSpan.Copy(apSource, apDest, count - 1); BufferSpan.Copy(apSource, apDest, count - 1);
}
AssertNotDefault(source, 1); AssertNotDefault(source, 1);
AssertNotDefault(dest, 1); AssertNotDefault(dest, 1);
@ -289,17 +301,13 @@ namespace ImageSharp.Tests.Common
[InlineData(1500)] [InlineData(1500)]
public void IntToInt(int count) public void IntToInt(int count)
{ {
int[] source = CreateTestInts(count+2); int[] source = CreateTestInts(count + 2);
int[] dest = new int[count + 5]; int[] dest = new int[count + 5];
fixed (int* pSource = source) BufferSpan<int> apSource = new BufferSpan<int>(source, 1);
fixed (int* pDest = dest) BufferSpan<int> apDest = new BufferSpan<int>(dest, 1);
{
BufferSpan<int> apSource = new BufferSpan<int>(source, pSource, 1);
BufferSpan<int> apDest = new BufferSpan<int>(dest, pDest, 1);
BufferSpan.Copy(apSource, apDest, count -1); BufferSpan.Copy(apSource, apDest, count - 1);
}
AssertNotDefault(source, 1); AssertNotDefault(source, 1);
AssertNotDefault(dest, 1); AssertNotDefault(dest, 1);
@ -317,17 +325,13 @@ namespace ImageSharp.Tests.Common
public void GenericToBytes(int count) public void GenericToBytes(int count)
{ {
int destCount = count * sizeof(Foo); int destCount = count * sizeof(Foo);
Foo[] source = Foo.CreateArray(count+2); Foo[] source = Foo.CreateArray(count + 2);
byte[] dest = new byte[destCount + sizeof(Foo)*2]; byte[] dest = new byte[destCount + sizeof(Foo) * 2];
fixed (Foo* pSource = source) BufferSpan<Foo> apSource = new BufferSpan<Foo>(source, 1);
fixed (byte* pDest = dest) BufferSpan<byte> apDest = new BufferSpan<byte>(dest, sizeof(Foo));
{
BufferSpan<Foo> apSource = new BufferSpan<Foo>(source, pSource, 1);
BufferSpan<byte> apDest = new BufferSpan<byte>(dest, pDest, sizeof(Foo));
BufferSpan.Copy(apSource, apDest, count - 1); BufferSpan.Copy(apSource.AsBytes(), apDest, (count - 1)*sizeof(Foo));
}
AssertNotDefault(source, 1); AssertNotDefault(source, 1);
@ -347,14 +351,10 @@ namespace ImageSharp.Tests.Common
AlignedFoo[] source = AlignedFoo.CreateArray(count + 2); AlignedFoo[] source = AlignedFoo.CreateArray(count + 2);
byte[] dest = new byte[destCount + sizeof(AlignedFoo) * 2]; byte[] dest = new byte[destCount + sizeof(AlignedFoo) * 2];
fixed (AlignedFoo* pSource = source) BufferSpan<AlignedFoo> apSource = new BufferSpan<AlignedFoo>(source, 1);
fixed (byte* pDest = dest) BufferSpan<byte> apDest = new BufferSpan<byte>(dest, sizeof(AlignedFoo));
{
BufferSpan<AlignedFoo> apSource = new BufferSpan<AlignedFoo>(source, pSource, 1);
BufferSpan<byte> apDest = new BufferSpan<byte>(dest, pDest, sizeof(AlignedFoo));
BufferSpan.Copy(apSource, apDest, count - 1); BufferSpan.Copy(apSource.AsBytes(), apDest, (count - 1) * sizeof(AlignedFoo));
}
AssertNotDefault(source, 1); AssertNotDefault(source, 1);
@ -371,17 +371,13 @@ namespace ImageSharp.Tests.Common
public void IntToBytes(int count) public void IntToBytes(int count)
{ {
int destCount = count * sizeof(int); int destCount = count * sizeof(int);
int[] source = CreateTestInts(count+2); int[] source = CreateTestInts(count + 2);
byte[] dest = new byte[destCount + sizeof(int) + 1]; byte[] dest = new byte[destCount + sizeof(int) + 1];
fixed (int* pSource = source) BufferSpan<int> apSource = new BufferSpan<int>(source);
fixed (byte* pDest = dest) BufferSpan<byte> apDest = new BufferSpan<byte>(dest);
{
BufferSpan<int> apSource = new BufferSpan<int>(source, pSource);
BufferSpan<byte> apDest = new BufferSpan<byte>(dest, pDest);
BufferSpan.Copy(apSource, apDest, count); BufferSpan.Copy(apSource.AsBytes(), apDest, count*sizeof(int));
}
AssertNotDefault(source, 1); AssertNotDefault(source, 1);
@ -398,15 +394,11 @@ namespace ImageSharp.Tests.Common
int srcCount = count * sizeof(Foo); int srcCount = count * sizeof(Foo);
byte[] source = CreateTestBytes(srcCount); byte[] source = CreateTestBytes(srcCount);
Foo[] dest = new Foo[count + 2]; Foo[] dest = new Foo[count + 2];
fixed(byte* pSource = source)
fixed (Foo* pDest = dest)
{
BufferSpan<byte> apSource = new BufferSpan<byte>(source, pSource);
BufferSpan<Foo> apDest = new BufferSpan<Foo>(dest, pDest);
BufferSpan.Copy(apSource, apDest, count); BufferSpan<byte> apSource = new BufferSpan<byte>(source);
} BufferSpan<Foo> apDest = new BufferSpan<Foo>(dest);
BufferSpan.Copy(apSource, apDest.AsBytes(), count*sizeof(Foo));
AssertNotDefault(source, sizeof(Foo) + 1); AssertNotDefault(source, sizeof(Foo) + 1);
AssertNotDefault(dest, 1); AssertNotDefault(dest, 1);
@ -422,10 +414,10 @@ namespace ImageSharp.Tests.Common
{ {
Color[] colors = { new Color(0, 1, 2, 3), new Color(4, 5, 6, 7), new Color(8, 9, 10, 11), }; Color[] colors = { new Color(0, 1, 2, 3), new Color(4, 5, 6, 7), new Color(8, 9, 10, 11), };
using (PinnedBuffer<Color> colorBuf = new PinnedBuffer<Color>(colors)) using (Buffer<Color> colorBuf = new Buffer<Color>(colors))
using (PinnedBuffer<byte> byteBuf = new PinnedBuffer<byte>(colors.Length*4)) using (Buffer<byte> byteBuf = new Buffer<byte>(colors.Length * 4))
{ {
BufferSpan.Copy<Color>(colorBuf, byteBuf, colorBuf.Length); BufferSpan.Copy(colorBuf.Span.AsBytes(), byteBuf, colorBuf.Length*sizeof(Color));
byte[] a = byteBuf.Array; byte[] a = byteBuf.Array;
@ -436,7 +428,6 @@ namespace ImageSharp.Tests.Common
} }
} }
internal static bool ElementsAreEqual(Foo[] array, byte[] rawArray, int index) internal static bool ElementsAreEqual(Foo[] array, byte[] rawArray, int index)
{ {
fixed (Foo* pArray = array) fixed (Foo* pArray = array)

100
tests/ImageSharp.Tests/Common/PinnedBufferTests.cs → tests/ImageSharp.Tests/Common/BufferTests.cs

@ -1,4 +1,5 @@
namespace ImageSharp.Tests.Common // ReSharper disable InconsistentNaming
namespace ImageSharp.Tests.Common
{ {
using System; using System;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
@ -9,21 +10,37 @@
using static TestStructs; using static TestStructs;
public unsafe class PinnedBufferTests public unsafe class BufferTests
{ {
// ReSharper disable once ClassNeverInstantiated.Local
private class Assert : Xunit.Assert
{
public static void SpanPointsTo<T>(BufferSpan<T> span, Buffer<T> buffer, int bufferOffset = 0)
where T : struct
{
ref T actual = ref span.DangerousGetPinnableReference();
ref T expected = ref Unsafe.Add(ref buffer[0], bufferOffset);
Assert.True(Unsafe.AreSame(ref expected, ref actual), "span does not point to the expected position");
}
public static void Equal(void* expected, void* actual)
{
Assert.Equal((IntPtr)expected, (IntPtr)actual);
}
}
[Theory] [Theory]
[InlineData(42)] [InlineData(42)]
[InlineData(1111)] [InlineData(1111)]
public void ConstructWithOwnArray(int count) public void ConstructWithOwnArray(int count)
{ {
using (PinnedBuffer<Foo> buffer = new PinnedBuffer<Foo>(count)) using (Buffer<Foo> buffer = new Buffer<Foo>(count))
{ {
Assert.False(buffer.IsDisposedOrLostArrayOwnership); Assert.False(buffer.IsDisposedOrLostArrayOwnership);
Assert.NotNull(buffer.Array); Assert.NotNull(buffer.Array);
Assert.Equal(count, buffer.Length); Assert.Equal(count, buffer.Length);
Assert.True(buffer.Array.Length >= count); Assert.True(buffer.Array.Length >= count);
VerifyPointer(buffer);
} }
} }
@ -33,13 +50,11 @@
public void ConstructWithExistingArray(int count) public void ConstructWithExistingArray(int count)
{ {
Foo[] array = new Foo[count]; Foo[] array = new Foo[count];
using (PinnedBuffer<Foo> buffer = new PinnedBuffer<Foo>(array)) using (Buffer<Foo> buffer = new Buffer<Foo>(array))
{ {
Assert.False(buffer.IsDisposedOrLostArrayOwnership); Assert.False(buffer.IsDisposedOrLostArrayOwnership);
Assert.Equal(array, buffer.Array); Assert.Equal(array, buffer.Array);
Assert.Equal(count, buffer.Length); Assert.Equal(count, buffer.Length);
VerifyPointer(buffer);
} }
} }
@ -49,7 +64,7 @@
public void Clear(int count) public void Clear(int count)
{ {
Foo[] a = { new Foo() { A = 1, B = 2 }, new Foo() { A = 3, B = 4 } }; Foo[] a = { new Foo() { A = 1, B = 2 }, new Foo() { A = 3, B = 4 } };
using (PinnedBuffer<Foo> buffer = new PinnedBuffer<Foo>(a)) using (Buffer<Foo> buffer = new Buffer<Foo>(a))
{ {
buffer.Clear(); buffer.Clear();
@ -63,7 +78,7 @@
{ {
for (int i = 0; i < 100; i++) for (int i = 0; i < 100; i++)
{ {
using (PinnedBuffer<int> buffer = PinnedBuffer<int>.CreateClean(42)) using (Buffer<int> buffer = Buffer<int>.CreateClean(42))
{ {
for (int j = 0; j < buffer.Length; j++) for (int j = 0; j < buffer.Length; j++)
{ {
@ -90,7 +105,7 @@
{ {
Foo[] a = Foo.CreateArray(length); Foo[] a = Foo.CreateArray(length);
using (PinnedBuffer<Foo> buffer = new PinnedBuffer<Foo>(a)) using (Buffer<Foo> buffer = new Buffer<Foo>(a))
{ {
Foo element = buffer[index]; Foo element = buffer[index];
@ -104,7 +119,7 @@
{ {
Foo[] a = Foo.CreateArray(length); Foo[] a = Foo.CreateArray(length);
using (PinnedBuffer<Foo> buffer = new PinnedBuffer<Foo>(a)) using (Buffer<Foo> buffer = new Buffer<Foo>(a))
{ {
buffer[index] = new Foo(666, 666); buffer[index] = new Foo(666, 666);
@ -116,24 +131,24 @@
[Fact] [Fact]
public void Dispose() public void Dispose()
{ {
PinnedBuffer<Foo> buffer = new PinnedBuffer<Foo>(42); Buffer<Foo> buffer = new Buffer<Foo>(42);
buffer.Dispose(); buffer.Dispose();
Assert.True(buffer.IsDisposedOrLostArrayOwnership); Assert.True(buffer.IsDisposedOrLostArrayOwnership);
} }
[Theory] [Theory]
[InlineData(7)] [InlineData(7)]
[InlineData(123)] [InlineData(123)]
public void CastToSpan(int bufferLength) public void CastToSpan(int bufferLength)
{ {
using (PinnedBuffer<Foo> buffer = new PinnedBuffer<Foo>(bufferLength)) using (Buffer<Foo> buffer = new Buffer<Foo>(bufferLength))
{ {
BufferSpan<Foo> span = buffer; BufferSpan<Foo> span = buffer;
Assert.Equal(buffer.Array, span.Array); Assert.Equal(buffer.Array, span.Array);
Assert.Equal(0, span.Start); Assert.Equal(0, span.Start);
Assert.Equal(buffer.Pointer, span.PointerAtOffset); Assert.SpanPointsTo(span, buffer);
Assert.Equal(span.Length, bufferLength); Assert.Equal(span.Length, bufferLength);
} }
} }
@ -141,13 +156,13 @@
[Fact] [Fact]
public void Span() public void Span()
{ {
using (PinnedBuffer<Foo> buffer = new PinnedBuffer<Foo>(42)) using (Buffer<Foo> buffer = new Buffer<Foo>(42))
{ {
BufferSpan<Foo> span = buffer.Span; BufferSpan<Foo> span = buffer.Span;
Assert.Equal(buffer.Array, span.Array); Assert.Equal(buffer.Array, span.Array);
Assert.Equal(0, span.Start); Assert.Equal(0, span.Start);
Assert.Equal(buffer.Pointer, span.PointerAtOffset); Assert.SpanPointsTo(span, buffer);
Assert.Equal(span.Length, 42); Assert.Equal(span.Length, 42);
} }
} }
@ -160,13 +175,13 @@
[InlineData(123, 17)] [InlineData(123, 17)]
public void WithStartOnly(int bufferLength, int start) public void WithStartOnly(int bufferLength, int start)
{ {
using (PinnedBuffer<Foo> buffer = new PinnedBuffer<Foo>(bufferLength)) using (Buffer<Foo> buffer = new Buffer<Foo>(bufferLength))
{ {
BufferSpan<Foo> span = buffer.Slice(start); BufferSpan<Foo> span = buffer.Slice(start);
Assert.Equal(buffer.Array, span.Array); Assert.Equal(buffer.Array, span.Array);
Assert.Equal(start, span.Start); Assert.Equal(start, span.Start);
Assert.Equal(buffer.Pointer + start * Unsafe.SizeOf<Foo>(), span.PointerAtOffset); Assert.SpanPointsTo(span, buffer, start);
Assert.Equal(span.Length, bufferLength - start); Assert.Equal(span.Length, bufferLength - start);
} }
} }
@ -176,13 +191,13 @@
[InlineData(123, 17, 42)] [InlineData(123, 17, 42)]
public void WithStartAndLength(int bufferLength, int start, int spanLength) public void WithStartAndLength(int bufferLength, int start, int spanLength)
{ {
using (PinnedBuffer<Foo> buffer = new PinnedBuffer<Foo>(bufferLength)) using (Buffer<Foo> buffer = new Buffer<Foo>(bufferLength))
{ {
BufferSpan<Foo> span = buffer.Slice(start, spanLength); BufferSpan<Foo> span = buffer.Slice(start, spanLength);
Assert.Equal(buffer.Array, span.Array); Assert.Equal(buffer.Array, span.Array);
Assert.Equal(start, span.Start); Assert.Equal(start, span.Start);
Assert.Equal(buffer.Pointer + start * Unsafe.SizeOf<Foo>(), span.PointerAtOffset); Assert.SpanPointsTo(span, buffer, start);
Assert.Equal(span.Length, spanLength); Assert.Equal(span.Length, spanLength);
} }
} }
@ -192,9 +207,9 @@
public void UnPinAndTakeArrayOwnership() public void UnPinAndTakeArrayOwnership()
{ {
Foo[] data = null; Foo[] data = null;
using (PinnedBuffer<Foo> buffer = new PinnedBuffer<Foo>(42)) using (Buffer<Foo> buffer = new Buffer<Foo>(42))
{ {
data = buffer.UnPinAndTakeArrayOwnership(); data = buffer.TakeArrayOwnership();
Assert.True(buffer.IsDisposedOrLostArrayOwnership); Assert.True(buffer.IsDisposedOrLostArrayOwnership);
} }
@ -202,10 +217,41 @@
Assert.True(data.Length >= 42); Assert.True(data.Length >= 42);
} }
private static void VerifyPointer(PinnedBuffer<Foo> buffer) public class Pin
{ {
IntPtr ptr = (IntPtr)Unsafe.AsPointer(ref buffer.Array[0]); [Fact]
Assert.Equal(ptr, buffer.Pointer); public void ReturnsPinnedPointerToTheBeginningOfArray()
{
using (Buffer<Foo> buffer = new Buffer<Foo>(42))
{
Foo* actual = (Foo*)buffer.Pin();
fixed (Foo* expected = buffer.Array)
{
Assert.Equal(expected, actual);
}
}
}
[Fact]
public void SecondCallReturnsTheSamePointer()
{
using (Buffer<Foo> buffer = new Buffer<Foo>(42))
{
IntPtr ptr1 = buffer.Pin();
IntPtr ptr2 = buffer.Pin();
Assert.Equal(ptr1, ptr2);
}
}
[Fact]
public void WhenCalledOnDisposedBuffer_ThrowsInvalidOperationException()
{
Buffer<Foo> buffer = new Buffer<Foo>(42);
buffer.Dispose();
Assert.Throws<InvalidOperationException>(() => buffer.Pin());
}
} }
} }
} }

2
tests/ImageSharp.Tests/Common/TestStructs.cs

@ -25,6 +25,8 @@ namespace ImageSharp.Tests.Common
} }
return result; return result;
} }
public override string ToString() => $"({this.A},{this.B})";
} }

Loading…
Cancel
Save