Browse Source

Merge remote-tracking branch 'remotes/origin/master' into 2017-v2

# Conflicts:
#	src/ImageSharp/project.json
af/merge-core
Scott Williams 9 years ago
parent
commit
2ae8ee223f
  1. 3
      .gitignore
  2. 4
      codecov.yml
  3. 4
      config.wyam
  4. 3
      input/about.md
  5. 249
      src/ImageSharp/Colors/Color.BulkOperations.cs
  6. 2
      src/ImageSharp/Colors/Color.cs
  7. 130
      src/ImageSharp/Common/Helpers/DebugGuard.cs
  8. 68
      src/ImageSharp/Common/Memory/BufferPointer.cs
  9. 39
      src/ImageSharp/Common/Memory/BufferPointer{T}.cs
  10. 52
      src/ImageSharp/Common/Memory/PinnedBuffer{T}.cs
  11. 11
      src/ImageSharp/Common/Memory/PixelDataPool{T}.cs
  12. 12
      src/ImageSharp/Image.cs
  13. 11
      src/ImageSharp/Image/ImageBase{TColor}.cs
  14. 155
      src/ImageSharp/Image/PixelAccessor{TColor}.cs
  15. 10
      src/ImageSharp/Image/PixelArea{TColor}.cs
  16. 52
      src/ImageSharp/ImageFrame.cs
  17. 153
      src/ImageSharp/PixelAccessor.cs
  18. 63
      tests/ImageSharp.Benchmarks/Color/Bulk/PackFromXyzw.cs
  19. 129
      tests/ImageSharp.Benchmarks/Color/Bulk/PixelAccessorVirtualCopy.cs
  20. 61
      tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4.cs
  21. 61
      tests/ImageSharp.Benchmarks/Color/Bulk/ToXyz.cs
  22. 70
      tests/ImageSharp.Benchmarks/Color/Bulk/ToXyzw.cs
  23. 43
      tests/ImageSharp.Benchmarks/General/ClearBuffer.cs
  24. 23
      tests/ImageSharp.Benchmarks/Image/EncodePng.cs
  25. 13
      tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj
  26. 21
      tests/ImageSharp.Sandbox46/Program.cs
  27. 169
      tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs
  28. 296
      tests/ImageSharp.Tests/Common/BufferPointerTests.cs
  29. 15
      tests/ImageSharp.Tests/Common/PinnedBufferTests.cs
  30. 40
      tests/ImageSharp.Tests/Common/PixelDataPoolTests.cs
  31. 25
      tests/ImageSharp.Tests/Formats/Jpg/JpegUtilityTestFixture.cs
  32. 44
      tests/ImageSharp.Tests/Image/PixelAccessorTests.cs
  33. 38
      tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs
  34. 3
      theme/index.cshtml

3
.gitignore

@ -215,4 +215,5 @@ artifacts/
*.csproj.bak
#CodeCoverage
**/CodeCoverage/*
**/CodeCoverage/*
docs/

4
codecov.yml

@ -0,0 +1,4 @@
ignore:
"src/ImageSharp/Common/Helpers/DebugGuard.cs"

4
config.wyam

@ -0,0 +1,4 @@
#recipe Docs
Settings[Keys.Host] = "imagesharp.org";
Settings[Keys.Title] = "Image Sharp";
FileSystem.OutputPath = "./docs";

3
input/about.md

@ -0,0 +1,3 @@
Title: About This Project
---
This project is awesome!

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

@ -0,0 +1,249 @@
// <copyright file="Color.BulkOperations.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp
{
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
/// <content>
/// Conains the definition of <see cref="BulkOperations"/>
/// </content>
public partial struct Color
{
/// <summary>
/// <see cref="BulkPixelOperations{TColor}"/> implementation optimized for <see cref="Color"/>.
/// </summary>
internal class BulkOperations : BulkPixelOperations<Color>
{
/// <summary>
/// SIMD optimized bulk implementation of <see cref="IPixel.PackFromVector4(Vector4)"/>
/// that works only with `count` divisible by <see cref="Vector{UInt32}.Count"/>.
/// </summary>
/// <param name="sourceColors">The <see cref="BufferPointer{T}"/> to the source colors.</param>
/// <param name="destVectors">The <see cref="BufferPointer{T}"/> to the dstination vectors.</param>
/// <param name="count">The number of pixels to convert.</param>
/// <remarks>
/// Implementation adapted from:
/// <see>
/// <cref>http://stackoverflow.com/a/5362789</cref>
/// </see>
/// TODO: We can replace this implementation in the future using new Vector API-s:
/// <see>
/// <cref>https://github.com/dotnet/corefx/issues/15957</cref>
/// </see>
/// </remarks>
internal static unsafe void ToVector4SimdAligned(
BufferPointer<Color> sourceColors,
BufferPointer<Vector4> destVectors,
int count)
{
int vecSize = Vector<uint>.Count;
DebugGuard.IsTrue(
count % vecSize == 0,
nameof(count),
"Argument 'count' should divisible by Vector<uint>.Count!");
Vector<float> bVec = new Vector<float>(256.0f / 255.0f);
Vector<float> magicFloat = new Vector<float>(32768.0f);
Vector<uint> magicInt = new Vector<uint>(1191182336); // reinterpreded value of 32768.0f
Vector<uint> mask = new Vector<uint>(255);
int unpackedRawCount = count * 4;
uint* src = (uint*)sourceColors.PointerAtOffset;
uint* srcEnd = src + count;
using (PinnedBuffer<uint> tempBuf = new PinnedBuffer<uint>(
unpackedRawCount + Vector<uint>.Count))
{
uint* tPtr = (uint*)tempBuf.Pointer;
uint[] temp = tempBuf.Array;
float[] fTemp = Unsafe.As<float[]>(temp);
UnpackedRGBA* dst = (UnpackedRGBA*)tPtr;
for (; src < srcEnd; src++, dst++)
{
// This call is the bottleneck now:
dst->Load(*src);
}
for (int i = 0; i < unpackedRawCount; i += vecSize)
{
Vector<uint> vi = new Vector<uint>(temp, i);
vi &= mask;
vi |= magicInt;
Vector<float> vf = Vector.AsVectorSingle(vi);
vf = (vf - magicFloat) * bVec;
vf.CopyTo(fTemp, i);
}
BufferPointer.Copy<uint>(tempBuf, (BufferPointer<byte>)destVectors, unpackedRawCount);
}
}
/// <inheritdoc />
internal override void ToVector4(BufferPointer<Color> sourceColors, BufferPointer<Vector4> destVectors, int count)
{
if (count < 256)
{
// Doesn't worth to bother with SIMD:
base.ToVector4(sourceColors, destVectors, count);
return;
}
int remainder = count % Vector<uint>.Count;
int alignedCount = count - remainder;
if (alignedCount > 0)
{
ToVector4SimdAligned(sourceColors, destVectors, alignedCount);
}
if (remainder > 0)
{
sourceColors = sourceColors.Slice(alignedCount);
destVectors = destVectors.Slice(alignedCount);
base.ToVector4(sourceColors, destVectors, remainder);
}
}
/// <inheritdoc />
internal override unsafe void PackFromXyzBytes(BufferPointer<byte> sourceBytes, BufferPointer<Color> destColors, int count)
{
byte* source = (byte*)sourceBytes;
byte* destination = (byte*)destColors;
for (int x = 0; x < count; x++)
{
Unsafe.Write(destination, (uint)(*source << 0 | *(source + 1) << 8 | *(source + 2) << 16 | 255 << 24));
source += 3;
destination += 4;
}
}
/// <inheritdoc />
internal override unsafe void ToXyzBytes(BufferPointer<Color> sourceColors, BufferPointer<byte> destBytes, int count)
{
byte* source = (byte*)sourceColors;
byte* destination = (byte*)destBytes;
for (int x = 0; x < count; x++)
{
*destination = *(source + 0);
*(destination + 1) = *(source + 1);
*(destination + 2) = *(source + 2);
source += 4;
destination += 3;
}
}
/// <inheritdoc />
internal override void PackFromXyzwBytes(BufferPointer<byte> sourceBytes, BufferPointer<Color> destColors, int count)
{
BufferPointer.Copy(sourceBytes, destColors, count);
}
/// <inheritdoc />
internal override void ToXyzwBytes(BufferPointer<Color> sourceColors, BufferPointer<byte> destBytes, int count)
{
BufferPointer.Copy(sourceColors, destBytes, count);
}
/// <inheritdoc />
internal override unsafe void PackFromZyxBytes(BufferPointer<byte> sourceBytes, BufferPointer<Color> destColors, int count)
{
byte* source = (byte*)sourceBytes;
byte* destination = (byte*)destColors;
for (int x = 0; x < count; x++)
{
Unsafe.Write(destination, (uint)(*(source + 2) << 0 | *(source + 1) << 8 | *source << 16 | 255 << 24));
source += 3;
destination += 4;
}
}
/// <inheritdoc />
internal override unsafe void ToZyxBytes(BufferPointer<Color> sourceColors, BufferPointer<byte> destBytes, int count)
{
byte* source = (byte*)sourceColors;
byte* destination = (byte*)destBytes;
for (int x = 0; x < count; x++)
{
*destination = *(source + 2);
*(destination + 1) = *(source + 1);
*(destination + 2) = *(source + 0);
source += 4;
destination += 3;
}
}
/// <inheritdoc />
internal override unsafe void PackFromZyxwBytes(BufferPointer<byte> sourceBytes, BufferPointer<Color> destColors, int count)
{
byte* source = (byte*)sourceBytes;
byte* destination = (byte*)destColors;
for (int x = 0; x < count; x++)
{
Unsafe.Write(destination, (uint)(*(source + 2) << 0 | *(source + 1) << 8 | *source << 16 | *(source + 3) << 24));
source += 4;
destination += 4;
}
}
/// <inheritdoc />
internal override unsafe void ToZyxwBytes(BufferPointer<Color> sourceColors, BufferPointer<byte> destBytes, int count)
{
byte* source = (byte*)sourceColors;
byte* destination = (byte*)destBytes;
for (int x = 0; x < count; x++)
{
*destination = *(source + 2);
*(destination + 1) = *(source + 1);
*(destination + 2) = *(source + 0);
*(destination + 3) = *(source + 3);
source += 4;
destination += 4;
}
}
/// <summary>
/// Value type to store <see cref="Color"/>-s unpacked into multiple <see cref="uint"/>-s.
/// </summary>
private struct UnpackedRGBA
{
private uint r;
private uint g;
private uint b;
private uint a;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Load(uint p)
{
this.r = p;
this.g = p >> Color.GreenShift;
this.b = p >> Color.BlueShift;
this.a = p >> Color.AlphaShift;
}
}
}
}
}

2
src/ImageSharp/Colors/Color.cs

@ -246,7 +246,7 @@ namespace ImageSharp
}
/// <inheritdoc />
public BulkPixelOperations<Color> CreateBulkOperations() => new BulkPixelOperations<Color>();
public BulkPixelOperations<Color> CreateBulkOperations() => new Color.BulkOperations();
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]

130
src/ImageSharp/Common/Helpers/DebugGuard.cs

@ -29,5 +29,135 @@ namespace ImageSharp
throw new ArgumentNullException(parameterName);
}
}
/// <summary>
/// Verifies that the specified value is less than a maximum value
/// and throws an exception if it is not.
/// </summary>
/// <param name="value">The target value, which should be validated.</param>
/// <param name="max">The maximum value.</param>
/// <param name="parameterName">The name of the parameter that is to be checked.</param>
/// <typeparam name="TValue">The type of the value.</typeparam>
/// <exception cref="ArgumentException">
/// <paramref name="value"/> is greater than the maximum value.
/// </exception>
[Conditional("DEBUG")]
public static void MustBeLessThan<TValue>(TValue value, TValue max, string parameterName)
where TValue : IComparable<TValue>
{
if (value.CompareTo(max) >= 0)
{
throw new ArgumentOutOfRangeException(parameterName, $"Value must be less than {max}.");
}
}
/// <summary>
/// Verifies that the specified value is less than or equal to a maximum value
/// and throws an exception if it is not.
/// </summary>
/// <param name="value">The target value, which should be validated.</param>
/// <param name="max">The maximum value.</param>
/// <param name="parameterName">The name of the parameter that is to be checked.</param>
/// <typeparam name="TValue">The type of the value.</typeparam>
/// <exception cref="ArgumentException">
/// <paramref name="value"/> is greater than the maximum value.
/// </exception>
[Conditional("DEBUG")]
public static void MustBeLessThanOrEqualTo<TValue>(TValue value, TValue max, string parameterName)
where TValue : IComparable<TValue>
{
if (value.CompareTo(max) > 0)
{
throw new ArgumentOutOfRangeException(parameterName, $"Value must be less than or equal to {max}.");
}
}
/// <summary>
/// Verifies that the specified value is greater than a minimum value
/// and throws an exception if it is not.
/// </summary>
/// <param name="value">The target value, which should be validated.</param>
/// <param name="min">The minimum value.</param>
/// <param name="parameterName">The name of the parameter that is to be checked.</param>
/// <typeparam name="TValue">The type of the value.</typeparam>
/// <exception cref="ArgumentException">
/// <paramref name="value"/> is less than the minimum value.
/// </exception>
[Conditional("DEBUG")]
public static void MustBeGreaterThan<TValue>(TValue value, TValue min, string parameterName)
where TValue : IComparable<TValue>
{
if (value.CompareTo(min) <= 0)
{
throw new ArgumentOutOfRangeException(
parameterName,
$"Value must be greater than {min}.");
}
}
/// <summary>
/// Verifies that the specified value is greater than or equal to a minimum value
/// and throws an exception if it is not.
/// </summary>
/// <param name="value">The target value, which should be validated.</param>
/// <param name="min">The minimum value.</param>
/// <param name="parameterName">The name of the parameter that is to be checked.</param>
/// <typeparam name="TValue">The type of the value.</typeparam>
/// <exception cref="ArgumentException">
/// <paramref name="value"/> is less than the minimum value.
/// </exception>
[Conditional("DEBUG")]
public static void MustBeGreaterThanOrEqualTo<TValue>(TValue value, TValue min, string parameterName)
where TValue : IComparable<TValue>
{
if (value.CompareTo(min) < 0)
{
throw new ArgumentOutOfRangeException(parameterName, $"Value must be greater than or equal to {min}.");
}
}
/// <summary>
/// Verifies, that the method parameter with specified target value is true
/// and throws an exception if it is found to be so.
/// </summary>
/// <param name="target">
/// The target value, which cannot be false.
/// </param>
/// <param name="parameterName">
/// The name of the parameter that is to be checked.
/// </param>
/// <param name="message">
/// The error message, if any to add to the exception.
/// </param>
/// <exception cref="ArgumentException">
/// <paramref name="target"/> is false
/// </exception>
[Conditional("DEBUG")]
public static void IsTrue(bool target, string parameterName, string message)
{
if (!target)
{
throw new ArgumentException(message, parameterName);
}
}
/// <summary>
/// Verifies, that the method parameter with specified target value is false
/// and throws an exception if it is found to be so.
/// </summary>
/// <param name="target">The target value, which cannot be true.</param>
/// <param name="parameterName">The name of the parameter that is to be checked.</param>
/// <param name="message">The error message, if any to add to the exception.</param>
/// <exception cref="ArgumentException">
/// <paramref name="target"/> is true
/// </exception>
[Conditional("DEBUG")]
public static void IsFalse(bool target, string parameterName, string message)
{
if (target)
{
throw new ArgumentException(message, parameterName);
}
}
}
}

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

@ -5,7 +5,10 @@
namespace ImageSharp
{
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
/// <summary>
/// Utility methods for <see cref="BufferPointer{T}"/>
@ -13,17 +16,9 @@ namespace ImageSharp
internal static class BufferPointer
{
/// <summary>
/// Gets a <see cref="BufferPointer{T}"/> to the beginning of the raw data in 'buffer'.
/// It's worth to use Marshal.Copy() or Buffer.BlockCopy() over this size.
/// </summary>
/// <typeparam name="T">The element type</typeparam>
/// <param name="buffer">The input <see cref="PinnedBuffer{T}"/></param>
/// <returns>The <see cref="BufferPointer{T}"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe BufferPointer<T> Slice<T>(this PinnedBuffer<T> buffer)
where T : struct
{
return new BufferPointer<T>(buffer.Array, (void*)buffer.Pointer);
}
private const int ByteCountThreshold = 1024;
/// <summary>
/// Copy 'count' number of elements of the same type from 'source' to 'dest'
@ -33,10 +28,10 @@ namespace ImageSharp
/// <param name="destination">The destination <see cref="BufferPointer{T}"/>.</param>
/// <param name="count">The number of elements to copy</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe void Copy<T>(BufferPointer<T> source, BufferPointer<T> destination, int count)
public static void Copy<T>(BufferPointer<T> source, BufferPointer<T> destination, int count)
where T : struct
{
Unsafe.CopyBlock((void*)source.PointerAtOffset, (void*)destination.PointerAtOffset, USizeOf<T>(count));
CopyImpl(source, destination, count);
}
/// <summary>
@ -47,10 +42,10 @@ namespace ImageSharp
/// <param name="destination">The destination buffer.</param>
/// <param name="countInSource">The number of elements to copy from 'source'</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe void Copy<T>(BufferPointer<T> source, BufferPointer<byte> destination, int countInSource)
public static void Copy<T>(BufferPointer<T> source, BufferPointer<byte> destination, int countInSource)
where T : struct
{
Unsafe.CopyBlock((void*)source.PointerAtOffset, (void*)destination.PointerAtOffset, USizeOf<T>(countInSource));
CopyImpl(source, destination, countInSource);
}
/// <summary>
@ -64,7 +59,16 @@ namespace ImageSharp
public static unsafe void Copy<T>(BufferPointer<byte> source, BufferPointer<T> destination, int countInDest)
where T : struct
{
Unsafe.CopyBlock((void*)source.PointerAtOffset, (void*)destination.PointerAtOffset, USizeOf<T>(countInDest));
int byteCount = SizeOf<T>(countInDest);
if (byteCount > (int)ByteCountThreshold)
{
Marshal.Copy(source.Array, source.Offset, destination.PointerAtOffset, byteCount);
}
else
{
Unsafe.CopyBlock((void*)destination.PointerAtOffset, (void*)source.PointerAtOffset, (uint)byteCount);
}
}
/// <summary>
@ -87,5 +91,39 @@ namespace ImageSharp
public static uint USizeOf<T>(int count)
where T : struct
=> (uint)SizeOf<T>(count);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static unsafe void CopyImpl<T, TDest>(BufferPointer<T> source, BufferPointer<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.Offset, destination.PointerAtOffset, count);
return;
}
else if (Unsafe.SizeOf<T>() == sizeof(int))
{
Marshal.Copy(Unsafe.As<int[]>(source.Array), source.Offset, destination.PointerAtOffset, count);
return;
}
else if (Unsafe.SizeOf<T>() == sizeof(short))
{
Marshal.Copy(Unsafe.As<short[]>(source.Array), source.Offset, destination.PointerAtOffset, count);
return;
}
else if (Unsafe.SizeOf<T>() == sizeof(byte))
{
Marshal.Copy(Unsafe.As<byte[]>(source.Array), source.Offset, destination.PointerAtOffset, count);
return;
}
}
Unsafe.CopyBlock((void*)destination.PointerAtOffset, (void*)source.PointerAtOffset, (uint)byteCount);
}
}
}

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

@ -63,6 +63,11 @@ namespace ImageSharp
/// </summary>
public int Offset { get; private set; }
/// <summary>
/// Gets the offset inside <see cref="Array"/> in bytes.
/// </summary>
public int ByteOffset => this.Offset * Unsafe.SizeOf<T>();
/// <summary>
/// Gets the pointer to the offseted array position
/// </summary>
@ -79,7 +84,7 @@ namespace ImageSharp
}
/// <summary>
/// Convertes <see cref="BufferPointer{T}"/> instance to a raw 'byte*' pointer
/// Converts <see cref="BufferPointer{T}"/> instance to a raw 'byte*' pointer
/// </summary>
/// <param name="bufferPointer">The <see cref="BufferPointer{T}"/> to convert</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@ -88,6 +93,21 @@ namespace ImageSharp
return (byte*)bufferPointer.PointerAtOffset;
}
/// <summary>
/// Converts <see cref="BufferPointer{T}"/> instance to <see cref="BufferPointer{Byte}"/>
/// setting it's <see cref="Offset"/> and <see cref="PointerAtOffset"/> to correct values.
/// </summary>
/// <param name="source">The <see cref="BufferPointer{T}"/> to convert</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static explicit operator BufferPointer<byte>(BufferPointer<T> source)
{
BufferPointer<byte> result = default(BufferPointer<byte>);
result.Array = Unsafe.As<byte[]>(source.Array);
result.Offset = source.Offset * Unsafe.SizeOf<T>();
result.PointerAtOffset = source.PointerAtOffset;
return result;
}
/// <summary>
/// Forms a slice out of the given BufferPointer, beginning at 'offset'.
/// </summary>
@ -102,5 +122,22 @@ namespace ImageSharp
result.PointerAtOffset = this.PointerAtOffset + (Unsafe.SizeOf<T>() * offset);
return result;
}
/// <summary>
/// Clears `count` elements beginning from the pointed position.
/// </summary>
/// <param name="count">The number of elements to clear</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Clear(int count)
{
if (count < 256)
{
Unsafe.InitBlock((void*)this.PointerAtOffset, 0, BufferPointer.USizeOf<T>(count));
}
else
{
System.Array.Clear(this.Array, this.Offset, count);
}
}
}
}

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

@ -7,6 +7,7 @@ namespace ImageSharp
{
using System;
using System.Buffers;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
/// <summary>
@ -23,7 +24,8 @@ namespace ImageSharp
private GCHandle handle;
/// <summary>
/// A value indicating whether this <see cref="PinnedBuffer{T}"/> instance should return the array to the pool.
/// A value indicating wheter <see cref="Array"/> should be returned to <see cref="PixelDataPool{T}"/>
/// when disposing this <see cref="PinnedBuffer{T}"/> instance.
/// </summary>
private bool isPoolingOwner;
@ -47,6 +49,7 @@ namespace ImageSharp
{
this.Count = array.Length;
this.Array = array;
this.isPoolingOwner = false;
this.Pin();
}
@ -64,6 +67,7 @@ namespace ImageSharp
this.Count = count;
this.Array = array;
this.isPoolingOwner = false;
this.Pin();
}
@ -95,9 +99,41 @@ namespace ImageSharp
/// </summary>
public IntPtr Pointer { get; private set; }
/// <summary>
/// Converts <see cref="PinnedBuffer{T}"/> to an <see cref="BufferPointer{T}"/>.
/// </summary>
/// <param name="buffer">The <see cref="PinnedBuffer{T}"/> to convert.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator BufferPointer<T>(PinnedBuffer<T> buffer)
{
return buffer.Slice();
}
/// <summary>
/// Gets a <see cref="BufferPointer{T}"/> to the beginning of the raw data of the buffer.
/// </summary>
/// <returns>The <see cref="BufferPointer{T}"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe BufferPointer<T> Slice()
{
return new BufferPointer<T>(this.Array, (void*)this.Pointer);
}
/// <summary>
/// Gets a <see cref="BufferPointer{T}"/> to an offseted position inside the buffer.
/// </summary>
/// <param name="offset">The offset</param>
/// <returns>The <see cref="BufferPointer{T}"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe BufferPointer<T> Slice(int offset)
{
return new BufferPointer<T>(this.Array, (void*)this.Pointer, offset);
}
/// <summary>
/// Disposes the <see cref="PinnedBuffer{T}"/> instance by unpinning the array, and returning the pooled buffer when necessary.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Dispose()
{
if (this.IsDisposedOrLostArrayOwnership)
@ -113,6 +149,7 @@ namespace ImageSharp
PixelDataPool<T>.Return(this.Array);
}
this.isPoolingOwner = false;
this.Array = null;
this.Count = 0;
@ -124,6 +161,7 @@ namespace ImageSharp
/// If <see cref="Array"/> is rented, it's the callers responsibility to return it to it's pool. (Most likely <see cref="PixelDataPool{T}"/>)
/// </summary>
/// <returns>The unpinned <see cref="Array"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public T[] UnPinAndTakeArrayOwnership()
{
if (this.IsDisposedOrLostArrayOwnership)
@ -135,12 +173,23 @@ namespace ImageSharp
this.UnPin();
T[] array = this.Array;
this.Array = null;
this.isPoolingOwner = false;
return array;
}
/// <summary>
/// Clears the buffer, filling elements between 0 and <see cref="Count"/>-1 with default(T)
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Clear()
{
this.Slice().Clear(this.Count);
}
/// <summary>
/// Pins <see cref="Array"/>.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void Pin()
{
this.handle = GCHandle.Alloc(this.Array, GCHandleType.Pinned);
@ -150,6 +199,7 @@ namespace ImageSharp
/// <summary>
/// Unpins <see cref="Array"/>.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void UnPin()
{
if (this.Pointer == IntPtr.Zero || !this.handle.IsAllocated)

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

@ -9,15 +9,14 @@ namespace ImageSharp
using System.Buffers;
/// <summary>
/// Provides a resource pool that enables reusing instances of value type arrays <see cref="T:T[]"/>.
/// <see cref="Rent(int)"/> will always return arrays initialized with 'default(T)'
/// Provides a resource pool that enables reusing instances of value type arrays for image data <see cref="T:T[]"/>.
/// </summary>
/// <typeparam name="T">The value type.</typeparam>
public static class PixelDataPool<T>
public class PixelDataPool<T>
where T : struct
{
/// <summary>
/// The <see cref="ArrayPool{T}"/> used to pool data.
/// The <see cref="ArrayPool{T}"/> which is not kept clean.
/// </summary>
private static readonly ArrayPool<T> ArrayPool = ArrayPool<T>.Create(CalculateMaxArrayLength(), 50);
@ -37,11 +36,11 @@ namespace ImageSharp
/// <param name="array">The array to return to the buffer pool.</param>
public static void Return(T[] array)
{
ArrayPool.Return(array, true);
ArrayPool.Return(array);
}
/// <summary>
/// Heuristically calculates a reasonable maxArrayLength value for the backing <see cref="ArrayPool"/>.
/// Heuristically calculates a reasonable maxArrayLength value for the backing <see cref="ArrayPool{T}"/>.
/// </summary>
/// <returns>The maxArrayLength value</returns>
internal static int CalculateMaxArrayLength()

12
src/ImageSharp/Image.cs

@ -223,17 +223,5 @@ namespace ImageSharp
: base(other)
{
}
/// <inheritdoc />
public override PixelAccessor<Color> Lock()
{
return new PixelAccessor(this);
}
/// <inheritdoc />
internal override ImageFrame<Color> ToFrame()
{
return new ImageFrame(this);
}
}
}

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

@ -60,6 +60,7 @@ namespace ImageSharp
{
this.Configuration = configuration ?? Configuration.Default;
this.InitPixels(width, height);
this.ClearPixels();
}
/// <summary>
@ -150,7 +151,7 @@ namespace ImageSharp
}
/// <inheritdoc/>
public virtual PixelAccessor<TColor> Lock()
public PixelAccessor<TColor> Lock()
{
return new PixelAccessor<TColor>(this);
}
@ -232,5 +233,13 @@ namespace ImageSharp
PixelDataPool<TColor>.Return(this.pixelBuffer);
this.pixelBuffer = null;
}
/// <summary>
/// Clears the pixel array.
/// </summary>
private void ClearPixels()
{
Array.Clear(this.pixelBuffer, 0, this.Width * this.Height);
}
}
}

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

@ -15,7 +15,7 @@ namespace ImageSharp
/// Provides per-pixel access to generic <see cref="Image{TColor}"/> pixels.
/// </summary>
/// <typeparam name="TColor">The pixel format.</typeparam>
public unsafe class PixelAccessor<TColor> : IDisposable
public sealed unsafe class PixelAccessor<TColor> : IDisposable
where TColor : struct, IPixel<TColor>
{
/// <summary>
@ -91,7 +91,7 @@ namespace ImageSharp
/// <summary>
/// Gets the pixel buffer array.
/// </summary>
public TColor[] PixelBuffer => this.pixelBuffer.Array;
public TColor[] PixelArray => this.pixelBuffer.Array;
/// <summary>
/// Gets the pointer to the pixel buffer.
@ -123,6 +123,8 @@ namespace ImageSharp
/// </summary>
public ParallelOptions ParallelOptions { get; }
private static BulkPixelOperations<TColor> Operations => BulkPixelOperations<TColor>.Instance;
/// <summary>
/// Gets or sets the pixel at the specified position.
/// </summary>
@ -236,6 +238,17 @@ namespace ImageSharp
Unsafe.InitBlock(this.pixelsBase, 0, (uint)(this.RowStride * this.Height));
}
/// <summary>
/// Gets a <see cref="BufferPointer{TColor}"/> to the row 'y' beginning from the pixel at 'x'.
/// </summary>
/// <param name="x">The x coordinate</param>
/// <param name="y">The y coordinate</param>
/// <returns>The <see cref="BufferPointer{TColor}"/></returns>
internal BufferPointer<TColor> GetRowPointer(int x, int y)
{
return this.pixelBuffer.Slice((y * this.Width) + x);
}
/// <summary>
/// Sets the pixel buffer in an unsafe manner. This should not be used unless you know what its doing!!!
/// </summary>
@ -270,24 +283,15 @@ namespace ImageSharp
/// <param name="targetY">The target row index.</param>
/// <param name="width">The width.</param>
/// <param name="height">The height.</param>
protected virtual void CopyFromZyx(PixelArea<TColor> area, int targetX, int targetY, int width, int height)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void CopyFromZyx(PixelArea<TColor> area, int targetX, int targetY, int width, int height)
{
TColor packed = default(TColor);
int size = Unsafe.SizeOf<TColor>();
for (int y = 0; y < height; y++)
{
byte* source = area.PixelBase + (y * area.RowStride);
byte* destination = this.GetRowPointer(targetX, targetY + y);
for (int x = 0; x < width; x++)
{
packed.PackFromBytes(*(source + 2), *(source + 1), *source, 255);
Unsafe.Write(destination, packed);
BufferPointer<byte> source = area.GetRowPointer(y);
BufferPointer<TColor> destination = this.GetRowPointer(targetX, targetY + y);
source += 3;
destination += size;
}
Operations.PackFromZyxBytes(source, destination, width);
}
}
@ -299,24 +303,15 @@ namespace ImageSharp
/// <param name="targetY">The target row index.</param>
/// <param name="width">The width.</param>
/// <param name="height">The height.</param>
protected virtual void CopyFromZyxw(PixelArea<TColor> area, int targetX, int targetY, int width, int height)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void CopyFromZyxw(PixelArea<TColor> area, int targetX, int targetY, int width, int height)
{
TColor packed = default(TColor);
int size = Unsafe.SizeOf<TColor>();
for (int y = 0; y < height; y++)
{
byte* source = area.PixelBase + (y * area.RowStride);
byte* destination = this.GetRowPointer(targetX, targetY + y);
for (int x = 0; x < width; x++)
{
packed.PackFromBytes(*(source + 2), *(source + 1), *source, *(source + 3));
Unsafe.Write(destination, packed);
BufferPointer<byte> source = area.GetRowPointer(y);
BufferPointer<TColor> destination = this.GetRowPointer(targetX, targetY + y);
source += 4;
destination += size;
}
Operations.PackFromZyxwBytes(source, destination, width);
}
}
@ -328,24 +323,15 @@ namespace ImageSharp
/// <param name="targetY">The target row index.</param>
/// <param name="width">The width.</param>
/// <param name="height">The height.</param>
protected virtual void CopyFromXyz(PixelArea<TColor> area, int targetX, int targetY, int width, int height)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void CopyFromXyz(PixelArea<TColor> area, int targetX, int targetY, int width, int height)
{
TColor packed = default(TColor);
int size = Unsafe.SizeOf<TColor>();
for (int y = 0; y < height; y++)
{
byte* source = area.PixelBase + (y * area.RowStride);
byte* destination = this.GetRowPointer(targetX, targetY + y);
for (int x = 0; x < width; x++)
{
packed.PackFromBytes(*source, *(source + 1), *(source + 2), 255);
Unsafe.Write(destination, packed);
BufferPointer<byte> source = area.GetRowPointer(y);
BufferPointer<TColor> destination = this.GetRowPointer(targetX, targetY + y);
source += 3;
destination += size;
}
Operations.PackFromXyzBytes(source, destination, width);
}
}
@ -357,24 +343,14 @@ namespace ImageSharp
/// <param name="targetY">The target row index.</param>
/// <param name="width">The width.</param>
/// <param name="height">The height.</param>
protected virtual void CopyFromXyzw(PixelArea<TColor> area, int targetX, int targetY, int width, int height)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void CopyFromXyzw(PixelArea<TColor> area, int targetX, int targetY, int width, int height)
{
TColor packed = default(TColor);
int size = Unsafe.SizeOf<TColor>();
for (int y = 0; y < height; y++)
{
byte* source = area.PixelBase + (y * area.RowStride);
byte* destination = this.GetRowPointer(targetX, targetY + y);
for (int x = 0; x < width; x++)
{
packed.PackFromBytes(*source, *(source + 1), *(source + 2), *(source + 3));
Unsafe.Write(destination, packed);
source += 4;
destination += size;
}
BufferPointer<byte> source = area.GetRowPointer(y);
BufferPointer<TColor> destination = this.GetRowPointer(targetX, targetY + y);
Operations.PackFromXyzwBytes(source, destination, width);
}
}
@ -386,16 +362,14 @@ namespace ImageSharp
/// <param name="sourceY">The source row index.</param>
/// <param name="width">The width.</param>
/// <param name="height">The height.</param>
protected virtual void CopyToZyx(PixelArea<TColor> area, int sourceX, int sourceY, int width, int height)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void CopyToZyx(PixelArea<TColor> area, int sourceX, int sourceY, int width, int height)
{
for (int y = 0; y < height; y++)
{
int offset = y * area.RowStride;
for (int x = 0; x < width; x++)
{
this[sourceX + x, sourceY + y].ToZyxBytes(area.Bytes, offset);
offset += 3;
}
BufferPointer<TColor> source = this.GetRowPointer(sourceX, sourceY + y);
BufferPointer<byte> destination = area.GetRowPointer(y);
Operations.ToZyxBytes(source, destination, width);
}
}
@ -407,16 +381,14 @@ namespace ImageSharp
/// <param name="sourceY">The source row index.</param>
/// <param name="width">The width.</param>
/// <param name="height">The height.</param>
protected virtual void CopyToZyxw(PixelArea<TColor> area, int sourceX, int sourceY, int width, int height)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void CopyToZyxw(PixelArea<TColor> area, int sourceX, int sourceY, int width, int height)
{
for (int y = 0; y < height; y++)
{
int offset = y * area.RowStride;
for (int x = 0; x < width; x++)
{
this[sourceX + x, sourceY + y].ToZyxwBytes(area.Bytes, offset);
offset += 4;
}
BufferPointer<TColor> source = this.GetRowPointer(sourceX, sourceY + y);
BufferPointer<byte> destination = area.GetRowPointer(y);
Operations.ToZyxwBytes(source, destination, width);
}
}
@ -428,16 +400,14 @@ namespace ImageSharp
/// <param name="sourceY">The source row index.</param>
/// <param name="width">The width.</param>
/// <param name="height">The height.</param>
protected virtual void CopyToXyz(PixelArea<TColor> area, int sourceX, int sourceY, int width, int height)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void CopyToXyz(PixelArea<TColor> area, int sourceX, int sourceY, int width, int height)
{
for (int y = 0; y < height; y++)
{
int offset = y * area.RowStride;
for (int x = 0; x < width; x++)
{
this[sourceX + x, sourceY + y].ToXyzBytes(area.Bytes, offset);
offset += 3;
}
BufferPointer<TColor> source = this.GetRowPointer(sourceX, sourceY + y);
BufferPointer<byte> destination = area.GetRowPointer(y);
Operations.ToXyzBytes(source, destination, width);
}
}
@ -449,32 +419,17 @@ namespace ImageSharp
/// <param name="sourceY">The source row index.</param>
/// <param name="width">The width.</param>
/// <param name="height">The height.</param>
protected virtual void CopyToXyzw(PixelArea<TColor> area, int sourceX, int sourceY, int width, int height)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void CopyToXyzw(PixelArea<TColor> area, int sourceX, int sourceY, int width, int height)
{
for (int y = 0; y < height; y++)
{
int offset = y * area.RowStride;
for (int x = 0; x < width; x++)
{
this[sourceX + x, sourceY + y].ToXyzwBytes(area.Bytes, offset);
offset += 4;
}
BufferPointer<TColor> source = this.GetRowPointer(sourceX, sourceY + y);
BufferPointer<byte> destination = area.GetRowPointer(y);
Operations.ToXyzwBytes(source, destination, width);
}
}
/// <summary>
/// Gets the pointer at the specified row.
/// </summary>
/// <param name="x">The column index.</param>
/// <param name="y">The row index.</param>
/// <returns>
/// The <see cref="T:byte*"/>.
/// </returns>
protected byte* GetRowPointer(int x, int y)
{
return this.pixelsBase + (((y * this.Width) + x) * Unsafe.SizeOf<TColor>());
}
private void SetPixelBufferUnsafe(int width, int height, TColor[] pixels)
{
this.SetPixelBufferUnsafe(width, height, new PinnedBuffer<TColor>(width * height, pixels));

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

@ -203,6 +203,16 @@ namespace ImageSharp
Unsafe.InitBlock(this.PixelBase, 0, (uint)(this.RowStride * this.Height));
}
/// <summary>
/// Gets a <see cref="BufferPointer{Byte}"/> to the row y.
/// </summary>
/// <param name="y">The y coordinate</param>
/// <returns>The <see cref="BufferPointer{Byte}"/></returns>
internal BufferPointer<byte> GetRowPointer(int y)
{
return this.byteBuffer.Slice(y * this.RowStride);
}
/// <summary>
/// Gets component count for the given order.
/// </summary>

52
src/ImageSharp/ImageFrame.cs

@ -1,52 +0,0 @@
// <copyright file="ImageFrame.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp
{
using System.Diagnostics;
/// <summary>
/// An optimized frame for the <see cref="Image"/> class.
/// </summary>
[DebuggerDisplay("ImageFrame: {Width}x{Height}")]
public sealed class ImageFrame : ImageFrame<Color>
{
/// <summary>
/// Initializes a new instance of the <see cref="ImageFrame"/> class.
/// </summary>
/// <param name="width">The width of the image in pixels.</param>
/// <param name="height">The height of the image in pixels.</param>
/// <param name="configuration">
/// The configuration providing initialization code which allows extending the library.
/// </param>
public ImageFrame(int width, int height, Configuration configuration = null)
: base(width, height, configuration)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ImageFrame"/> class.
/// </summary>
/// <param name="image">
/// The image to create the frame from.
/// </param>
public ImageFrame(ImageBase<Color> image)
: base(image)
{
}
/// <inheritdoc />
public override PixelAccessor<Color> Lock()
{
return new PixelAccessor(this);
}
/// <inheritdoc />
internal override ImageFrame<Color> Clone()
{
return new ImageFrame(this);
}
}
}

153
src/ImageSharp/PixelAccessor.cs

@ -1,153 +0,0 @@
// <copyright file="PixelAccessor.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp
{
using System.Runtime.CompilerServices;
/// <summary>
/// An optimized pixel accessor for the <see cref="Image"/> class.
/// </summary>
public sealed unsafe class PixelAccessor : PixelAccessor<Color>
{
/// <summary>
/// Initializes a new instance of the <see cref="PixelAccessor"/> class.
/// </summary>
/// <param name="image">The image to provide pixel access for.</param>
public PixelAccessor(ImageBase<Color> image)
: base(image)
{
}
/// <inheritdoc />
protected override void CopyFromXyzw(PixelArea<Color> area, int targetX, int targetY, int width, int height)
{
uint byteCount = (uint)width * 4;
for (int y = 0; y < height; y++)
{
byte* source = area.PixelBase + (y * area.RowStride);
byte* destination = this.GetRowPointer(targetX, targetY + y);
Unsafe.CopyBlock(destination, source, byteCount);
}
}
/// <inheritdoc />
protected override void CopyFromXyz(PixelArea<Color> area, int targetX, int targetY, int width, int height)
{
for (int y = 0; y < height; y++)
{
byte* source = area.PixelBase + (y * area.RowStride);
byte* destination = this.GetRowPointer(targetX, targetY + y);
for (int x = 0; x < width; x++)
{
Unsafe.Write(destination, (uint)(*source << 0 | *(source + 1) << 8 | *(source + 2) << 16 | 255 << 24));
source += 3;
destination += 4;
}
}
}
/// <inheritdoc />
protected override void CopyFromZyx(PixelArea<Color> area, int targetX, int targetY, int width, int height)
{
for (int y = 0; y < height; y++)
{
byte* source = area.PixelBase + (y * area.RowStride);
byte* destination = this.GetRowPointer(targetX, targetY + y);
for (int x = 0; x < width; x++)
{
Unsafe.Write(destination, (uint)(*(source + 2) << 0 | *(source + 1) << 8 | *source << 16 | 255 << 24));
source += 3;
destination += 4;
}
}
}
/// <inheritdoc />
protected override void CopyFromZyxw(PixelArea<Color> area, int targetX, int targetY, int width, int height)
{
for (int y = 0; y < height; y++)
{
byte* source = area.PixelBase + (y * area.RowStride);
byte* destination = this.GetRowPointer(targetX, targetY + y);
for (int x = 0; x < width; x++)
{
Unsafe.Write(destination, (uint)(*(source + 2) << 0 | *(source + 1) << 8 | *source << 16 | *(source + 3) << 24));
source += 4;
destination += 4;
}
}
}
/// <inheritdoc />
protected override void CopyToZyx(PixelArea<Color> area, int sourceX, int sourceY, int width, int height)
{
for (int y = 0; y < height; y++)
{
byte* source = this.GetRowPointer(sourceX, sourceY + y);
byte* destination = area.PixelBase + (y * area.RowStride);
for (int x = 0; x < width; x++)
{
*destination = *(source + 2);
*(destination + 1) = *(source + 1);
*(destination + 2) = *(source + 0);
source += 4;
destination += 3;
}
}
}
/// <inheritdoc />
protected override void CopyToXyz(PixelArea<Color> area, int sourceX, int sourceY, int width, int height)
{
for (int y = 0; y < height; y++)
{
byte* source = this.GetRowPointer(sourceX, sourceY + y);
byte* destination = area.PixelBase + (y * area.RowStride);
for (int x = 0; x < width; x++)
{
*destination = *(source + 0);
*(destination + 1) = *(source + 1);
*(destination + 2) = *(source + 2);
source += 4;
destination += 3;
}
}
}
/// <inheritdoc />
protected override void CopyToZyxw(PixelArea<Color> area, int sourceX, int sourceY, int width, int height)
{
for (int y = 0; y < height; y++)
{
byte* source = this.GetRowPointer(sourceX, sourceY + y);
byte* destination = area.PixelBase + (y * area.RowStride);
for (int x = 0; x < width; x++)
{
*destination = *(source + 2);
*(destination + 1) = *(source + 1);
*(destination + 2) = *(source + 0);
*(destination + 3) = *(source + 3);
source += 4;
destination += 4;
}
}
}
}
}

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

@ -0,0 +1,63 @@
// ReSharper disable InconsistentNaming
namespace ImageSharp.Benchmarks.Color.Bulk
{
using BenchmarkDotNet.Attributes;
using Color = ImageSharp.Color;
public abstract class PackFromXyzw<TColor>
where TColor : struct, IPixel<TColor>
{
private PinnedBuffer<TColor> destination;
private PinnedBuffer<byte> source;
[Params(16, 128, 1024)]
public int Count { get; set; }
[Setup]
public void Setup()
{
this.destination = new PinnedBuffer<TColor>(this.Count);
this.source = new PinnedBuffer<byte>(this.Count * 4);
}
[Cleanup]
public void Cleanup()
{
this.destination.Dispose();
this.source.Dispose();
}
[Benchmark(Baseline = true)]
public void PerElement()
{
byte[] s = this.source.Array;
TColor[] d = this.destination.Array;
for (int i = 0; i < this.Count; i++)
{
int i4 = i * 4;
TColor c = default(TColor);
c.PackFromBytes(s[i4], s[i4 + 1], s[i4 + 2], s[i4 + 3]);
d[i] = c;
}
}
[Benchmark]
public void CommonBulk()
{
new BulkPixelOperations<TColor>().PackFromXyzwBytes(this.source, this.destination, this.Count);
}
[Benchmark]
public void OptimizedBulk()
{
BulkPixelOperations<TColor>.Instance.PackFromXyzwBytes(this.source, this.destination, this.Count);
}
}
public class PackFromXyzw_Color : PackFromXyzw<Color>
{
}
}

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

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

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

@ -0,0 +1,61 @@
// ReSharper disable InconsistentNaming
namespace ImageSharp.Benchmarks.Color.Bulk
{
using System.Numerics;
using BenchmarkDotNet.Attributes;
public abstract class ToVector4<TColor>
where TColor : struct, IPixel<TColor>
{
private PinnedBuffer<TColor> source;
private PinnedBuffer<Vector4> destination;
[Params(64, 300, 1024)]
public int Count { get; set; }
[Setup]
public void Setup()
{
this.source = new PinnedBuffer<TColor>(this.Count);
this.destination = new PinnedBuffer<Vector4>(this.Count);
}
[Cleanup]
public void Cleanup()
{
this.source.Dispose();
this.destination.Dispose();
}
[Benchmark(Baseline = true)]
public void PerElement()
{
TColor[] s = this.source.Array;
Vector4[] d = this.destination.Array;
for (int i = 0; i < this.Count; i++)
{
TColor c = s[i];
d[i] = c.ToVector4();
}
}
[Benchmark]
public void CommonBulk()
{
new BulkPixelOperations<TColor>().ToVector4(this.source, this.destination, this.Count);
}
[Benchmark]
public void OptimizedBulk()
{
BulkPixelOperations<TColor>.Instance.ToVector4(this.source, this.destination, this.Count);
}
}
public class ToVector4_Color : ToVector4<ImageSharp.Color>
{
}
}

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

@ -0,0 +1,61 @@
// ReSharper disable InconsistentNaming
namespace ImageSharp.Benchmarks.Color.Bulk
{
using BenchmarkDotNet.Attributes;
using Color = ImageSharp.Color;
public abstract class ToXyz<TColor>
where TColor : struct, IPixel<TColor>
{
private PinnedBuffer<TColor> source;
private PinnedBuffer<byte> destination;
[Params(16, 128, 1024)]
public int Count { get; set; }
[Setup]
public void Setup()
{
this.source = new PinnedBuffer<TColor>(this.Count);
this.destination = new PinnedBuffer<byte>(this.Count * 3);
}
[Cleanup]
public void Cleanup()
{
this.source.Dispose();
this.destination.Dispose();
}
[Benchmark(Baseline = true)]
public void PerElement()
{
TColor[] s = this.source.Array;
byte[] d = this.destination.Array;
for (int i = 0; i < this.Count; i++)
{
TColor c = s[i];
c.ToXyzBytes(d, i * 4);
}
}
[Benchmark]
public void CommonBulk()
{
new BulkPixelOperations<TColor>().ToXyzBytes(this.source, this.destination, this.Count);
}
[Benchmark]
public void OptimizedBulk()
{
BulkPixelOperations<TColor>.Instance.ToXyzBytes(this.source, this.destination, this.Count);
}
}
public class ToXyz_Color : ToXyz<Color>
{
}
}

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

@ -0,0 +1,70 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
// ReSharper disable InconsistentNaming
namespace ImageSharp.Benchmarks.Color.Bulk
{
using BenchmarkDotNet.Attributes;
using Color = ImageSharp.Color;
public abstract class ToXyzw<TColor>
where TColor : struct, IPixel<TColor>
{
private PinnedBuffer<TColor> source;
private PinnedBuffer<byte> destination;
[Params(16, 128, 1024)]
public int Count { get; set; }
[Setup]
public void Setup()
{
this.source = new PinnedBuffer<TColor>(this.Count);
this.destination = new PinnedBuffer<byte>(this.Count * 4);
}
[Cleanup]
public void Cleanup()
{
this.source.Dispose();
this.destination.Dispose();
}
[Benchmark(Baseline = true)]
public void PerElement()
{
TColor[] s = this.source.Array;
byte[] d = this.destination.Array;
for (int i = 0; i < this.Count; i++)
{
TColor c = s[i];
c.ToXyzwBytes(d, i * 4);
}
}
[Benchmark]
public void CommonBulk()
{
new BulkPixelOperations<TColor>().ToXyzwBytes(this.source, this.destination, this.Count);
}
[Benchmark]
public void OptimizedBulk()
{
BulkPixelOperations<TColor>.Instance.ToXyzwBytes(this.source, this.destination, this.Count);
}
}
public class ToXyzw_Color : ToXyzw<Color>
{
}
public class ToXyzw_Argb : ToXyzw<Argb>
{
}
}

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

@ -0,0 +1,43 @@
// ReSharper disable InconsistentNaming
namespace ImageSharp.Benchmarks.General
{
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using BenchmarkDotNet.Attributes;
using Color = ImageSharp.Color;
public unsafe class ClearBuffer
{
private PinnedBuffer<Color> buffer;
[Params(32, 128, 512)]
public int Count { get; set; }
[Setup]
public void Setup()
{
this.buffer = new PinnedBuffer<ImageSharp.Color>(this.Count);
}
[Cleanup]
public void Cleanup()
{
this.buffer.Dispose();
}
[Benchmark(Baseline = true)]
public void Array_Clear()
{
Array.Clear(this.buffer.Array, 0, this.Count);
}
[Benchmark]
public void Unsafe_InitBlock()
{
Unsafe.InitBlock((void*)this.buffer.Pointer, default(byte), (uint)this.Count*sizeof(uint));
}
}
}

23
tests/ImageSharp.Benchmarks/Image/EncodePng.cs

@ -10,6 +10,10 @@ namespace ImageSharp.Benchmarks.Image
using System.IO;
using BenchmarkDotNet.Attributes;
using ImageSharp.Formats;
using ImageSharp.Quantizers;
using CoreImage = ImageSharp.Image;
public class EncodePng : BenchmarkBase
@ -19,12 +23,21 @@ namespace ImageSharp.Benchmarks.Image
private Image bmpDrawing;
private CoreImage bmpCore;
[Params(false)]
public bool LargeImage { get; set; }
[Params(false)]
public bool UseOctreeQuantizer { get; set; }
[Setup]
public void ReadImages()
{
if (this.bmpStream == null)
{
this.bmpStream = File.OpenRead("../ImageSharp.Tests/TestImages/Formats/Bmp/Car.bmp");
string path = this.LargeImage
? "../ImageSharp.Tests/TestImages/Formats/Jpg/baseline/jpeg420exif.jpg"
: "../ImageSharp.Tests/TestImages/Formats/Bmp/Car.bmp";
this.bmpStream = File.OpenRead(path);
this.bmpCore = new CoreImage(this.bmpStream);
this.bmpStream.Position = 0;
this.bmpDrawing = Image.FromStream(this.bmpStream);
@ -53,7 +66,13 @@ namespace ImageSharp.Benchmarks.Image
{
using (MemoryStream memoryStream = new MemoryStream())
{
this.bmpCore.SaveAsPng(memoryStream);
Quantizer<ImageSharp.Color> quantizer = this.UseOctreeQuantizer
? (Quantizer<ImageSharp.Color>)
new OctreeQuantizer<ImageSharp.Color>()
: new PaletteQuantizer<ImageSharp.Color>();
PngEncoderOptions options = new PngEncoderOptions() { Quantizer = quantizer };
this.bmpCore.SaveAsPng(memoryStream, options);
}
}
}

13
tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj

@ -206,12 +206,12 @@
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="..\ImageSharp.Benchmarks\Color\Bulk\PixelAccessorVirtualCopy.cs">
<Link>Benchmarks\PixelAccessorVirtualCopy.cs</Link>
</Compile>
<Compile Include="..\ImageSharp.Tests\Colors\BulkPixelOperationsTests.cs">
<Link>Tests\Colors\BulkPixelOperationsTests.cs</Link>
</Compile>
<Compile Include="..\ImageSharp.Tests\Common\BufferPointerTests.cs">
<Link>Tests\Common\BufferPointerTests.cs</Link>
</Compile>
<Compile Include="..\ImageSharp.Tests\Common\PinnedBufferTests.cs">
<Link>Tests\Common\PinnedBufferTests.cs</Link>
</Compile>
@ -272,6 +272,9 @@
<Compile Include="..\ImageSharp.Tests\TestImages.cs">
<Link>Tests\TestImages.cs</Link>
</Compile>
<Compile Include="..\ImageSharp.Tests\TestUtilities\ApproximateFloatComparer.cs">
<Link>Tests\TestUtilities\ApproximateFloatComparer.cs</Link>
</Compile>
<Compile Include="..\ImageSharp.Tests\TestUtilities\Attributes\ImageDataAttributeBase.cs">
<Link>Tests\TestUtilities\Attributes\ImageDataAttributeBase.cs</Link>
</Compile>
@ -340,7 +343,9 @@
<ItemGroup>
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
</ItemGroup>
<ItemGroup />
<ItemGroup>
<Folder Include="Benchmarks\" />
</ItemGroup>
<ItemGroup>
<Analyzer Include="..\..\packages\Microsoft.CodeAnalysis.Analyzers.1.1.0\analyzers\dotnet\cs\Microsoft.CodeAnalysis.Analyzers.dll" />
<Analyzer Include="..\..\packages\Microsoft.CodeAnalysis.Analyzers.1.1.0\analyzers\dotnet\cs\Microsoft.CodeAnalysis.CSharp.Analyzers.dll" />

21
tests/ImageSharp.Sandbox46/Program.cs

@ -7,9 +7,9 @@ namespace ImageSharp.Sandbox46
{
using System;
using System.Runtime.DesignerServices;
using ImageSharp.Benchmarks.Color.Bulk;
using ImageSharp.Tests;
using ImageSharp.Tests.Colors;
using Xunit.Abstractions;
@ -38,21 +38,18 @@ namespace ImageSharp.Sandbox46
public static void Main(string[] args)
{
// RunDecodeJpegProfilingTests();
TestPixelAccessorCopyFromXyzw();
RunToVector4ProfilingTest();
Console.ReadLine();
}
private static void TestPixelAccessorCopyFromXyzw()
private static void RunToVector4ProfilingTest()
{
PixelAccessorVirtualCopy benchmark = new PixelAccessorVirtualCopy();
benchmark.Width = 64;
benchmark.Setup();
benchmark.CopyRawUnsafeInlined();
benchmark.Cleanup();
BulkPixelOperationsTests.Color tests = new BulkPixelOperationsTests.Color(new ConsoleOutput());
tests.Benchmark_ToVector4();
}
private static void RunDecodeJpegProfilingTests()
{
Console.WriteLine("RunDecodeJpegProfilingTests...");

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

@ -1,21 +1,71 @@
namespace ImageSharp.Tests.Colors
// ReSharper disable InconsistentNaming
// ReSharper disable AccessToDisposedClosure
namespace ImageSharp.Tests.Colors
{
using System;
using System.Numerics;
using Xunit;
using Xunit.Abstractions;
public class BulkPixelOperationsTests
{
public class Color : BulkPixelOperationsTests<ImageSharp.Color>
{
public Color(ITestOutputHelper output)
: base(output)
{
}
// 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 };
[Fact]
public void IsSpecialImplementation()
{
Assert.IsType<ImageSharp.Color.BulkOperations>(BulkPixelOperations<ImageSharp.Color>.Instance);
}
[Fact]
public void ToVector4SimdAligned()
{
ImageSharp.Color[] source = CreatePixelTestData(64);
Vector4[] expected = CreateExpectedVector4Data(source);
TestOperation(
source,
expected,
(s, d) => ImageSharp.Color.BulkOperations.ToVector4SimdAligned(s, d, 64)
);
}
// [Fact] // Profiling benchmark - enable manually!
public void Benchmark_ToVector4()
{
int times = 200000;
int count = 1024;
using (PinnedBuffer<ImageSharp.Color> source = new PinnedBuffer<ImageSharp.Color>(count))
using (PinnedBuffer<Vector4> dest = new PinnedBuffer<Vector4>(count))
{
this.Measure(
times,
() =>
{
BulkPixelOperations<ImageSharp.Color>.Instance.ToVector4(source, dest, count);
});
}
}
}
public class Argb : BulkPixelOperationsTests<ImageSharp.Argb>
{
// For 4.6 test runner MemberData does not work without redeclaring the public field in the derived test class:
public Argb(ITestOutputHelper output)
: base(output)
{
}
public static new TheoryData<int> ArraySizesData => new TheoryData<int> { 7, 16, 1111 };
}
@ -28,46 +78,65 @@
}
}
public abstract class BulkPixelOperationsTests<TColor>
public abstract class BulkPixelOperationsTests<TColor> : MeasureFixture
where TColor : struct, IPixel<TColor>
{
protected BulkPixelOperationsTests(ITestOutputHelper output)
: base(output)
{
}
public static TheoryData<int> ArraySizesData => new TheoryData<int> { 7, 16, 1111 };
[Theory]
[MemberData(nameof(ArraySizesData))]
public void PackFromVector4(int count)
private static BulkPixelOperations<TColor> Operations => BulkPixelOperations<TColor>.Instance;
internal static TColor[] CreateExpectedPixelData(Vector4[] source)
{
Vector4[] source = CreateVector4TestData(count);
TColor[] expected = new TColor[count];
TColor[] expected = new TColor[source.Length];
for (int i = 0; i < count; i++)
for (int i = 0; i < expected.Length; i++)
{
expected[i].PackFromVector4(source[i]);
}
return expected;
}
[Theory]
[MemberData(nameof(ArraySizesData))]
public void PackFromVector4(int count)
{
Vector4[] source = CreateVector4TestData(count);
TColor[] expected = CreateExpectedPixelData(source);
TestOperation(
source,
expected,
(ops, s, d) => ops.PackFromVector4(s, d, count)
(s, d) => Operations.PackFromVector4(s, d, count)
);
}
[Theory]
[MemberData(nameof(ArraySizesData))]
public void PackToVector4(int count)
internal static Vector4[] CreateExpectedVector4Data(TColor[] source)
{
TColor[] source = CreatePixelTestData(count);
Vector4[] expected = new Vector4[count];
Vector4[] expected = new Vector4[source.Length];
for (int i = 0; i < count; i++)
for (int i = 0; i < expected.Length; i++)
{
expected[i] = source[i].ToVector4();
}
return expected;
}
[Theory]
[MemberData(nameof(ArraySizesData))]
public void ToVector4(int count)
{
TColor[] source = CreatePixelTestData(count);
Vector4[] expected = CreateExpectedVector4Data(source);
TestOperation(
source,
expected,
(ops, s, d) => ops.ToVector4(s, d, count)
(s, d) => Operations.ToVector4(s, d, count)
);
}
@ -89,13 +158,13 @@
TestOperation(
source,
expected,
(ops, s, d) => ops.PackFromXyzBytes(s, d, count)
(s, d) => Operations.PackFromXyzBytes(s, d, count)
);
}
[Theory]
[MemberData(nameof(ArraySizesData))]
public void PackToXyzBytes(int count)
public void ToXyzBytes(int count)
{
TColor[] source = CreatePixelTestData(count);
byte[] expected = new byte[count * 3];
@ -109,7 +178,7 @@
TestOperation(
source,
expected,
(ops, s, d) => ops.ToXyzBytes(s, d, count)
(s, d) => Operations.ToXyzBytes(s, d, count)
);
}
@ -130,13 +199,13 @@
TestOperation(
source,
expected,
(ops, s, d) => ops.PackFromXyzwBytes(s, d, count)
(s, d) => Operations.PackFromXyzwBytes(s, d, count)
);
}
[Theory]
[MemberData(nameof(ArraySizesData))]
public void PackToXyzwBytes(int count)
public void ToXyzwBytes(int count)
{
TColor[] source = CreatePixelTestData(count);
byte[] expected = new byte[count * 4];
@ -150,7 +219,7 @@
TestOperation(
source,
expected,
(ops, s, d) => ops.ToXyzwBytes(s, d, count)
(s, d) => Operations.ToXyzwBytes(s, d, count)
);
}
@ -171,13 +240,13 @@
TestOperation(
source,
expected,
(ops, s, d) => ops.PackFromZyxBytes(s, d, count)
(s, d) => Operations.PackFromZyxBytes(s, d, count)
);
}
[Theory]
[MemberData(nameof(ArraySizesData))]
public void PackToZyxBytes(int count)
public void ToZyxBytes(int count)
{
TColor[] source = CreatePixelTestData(count);
byte[] expected = new byte[count * 3];
@ -191,7 +260,7 @@
TestOperation(
source,
expected,
(ops, s, d) => ops.ToZyxBytes(s, d, count)
(s, d) => Operations.ToZyxBytes(s, d, count)
);
}
@ -212,13 +281,13 @@
TestOperation(
source,
expected,
(ops, s, d) => ops.PackFromZyxwBytes(s, d, count)
(s, d) => Operations.PackFromZyxwBytes(s, d, count)
);
}
[Theory]
[MemberData(nameof(ArraySizesData))]
public void PackToZyxwBytes(int count)
public void ToZyxwBytes(int count)
{
TColor[] source = CreatePixelTestData(count);
byte[] expected = new byte[count * 4];
@ -232,7 +301,7 @@
TestOperation(
source,
expected,
(ops, s, d) => ops.ToZyxwBytes(s, d, count)
(s, d) => Operations.ToZyxwBytes(s, d, count)
);
}
@ -262,33 +331,51 @@
this.ExpectedDestBuffer.Dispose();
}
private const float Tolerance = 0.0001f;
public void Verify()
{
int count = this.ExpectedDestBuffer.Count;
TDest[] expected = this.ExpectedDestBuffer.Array;
TDest[] actual = this.ActualDestBuffer.Array;
for (int i = 0; i < count; i++)
if (typeof(TDest) == typeof(Vector4))
{
Vector4[] expected = this.ExpectedDestBuffer.Array as Vector4[];
Vector4[] actual = this.ActualDestBuffer.Array as Vector4[];
for (int i = 0; i < count; i++)
{
// ReSharper disable PossibleNullReferenceException
Assert.Equal(expected[i], actual[i], new ApproximateFloatComparer(0.001f));
// ReSharper restore PossibleNullReferenceException
}
}
else
{
Assert.Equal(expected[i], actual[i]);
TDest[] expected = this.ExpectedDestBuffer.Array;
TDest[] actual = this.ActualDestBuffer.Array;
for (int i = 0; i < count; i++)
{
Assert.Equal(expected[i], actual[i]);
}
}
}
}
private static void TestOperation<TSource, TDest>(
internal static void TestOperation<TSource, TDest>(
TSource[] source,
TDest[] expected,
Action<BulkPixelOperations<TColor>, BufferPointer<TSource>, BufferPointer<TDest>> action)
Action<BufferPointer<TSource>, BufferPointer<TDest>> action)
where TSource : struct
where TDest : struct
{
using (var buffers = new TestBuffers<TSource, TDest>(source, expected))
{
action(BulkPixelOperations<TColor>.Instance, buffers.Source, buffers.ActualDest);
action(buffers.Source, buffers.ActualDest);
buffers.Verify();
}
}
private static Vector4[] CreateVector4TestData(int length)
internal static Vector4[] CreateVector4TestData(int length)
{
Vector4[] result = new Vector4[length];
Random rnd = new Random(42); // Deterministic random values
@ -300,7 +387,7 @@
return result;
}
private static TColor[] CreatePixelTestData(int length)
internal static TColor[] CreatePixelTestData(int length)
{
TColor[] result = new TColor[length];
@ -315,7 +402,7 @@
return result;
}
private static byte[] CreateByteTestData(int length)
internal static byte[] CreateByteTestData(int length)
{
byte[] result = new byte[length];
Random rnd = new Random(42); // Deterministic random values
@ -326,8 +413,8 @@
}
return result;
}
private static Vector4 GetVector(Random rnd)
internal static Vector4 GetVector(Random rnd)
{
return new Vector4(
(float)rnd.NextDouble(),

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

@ -15,16 +15,68 @@ namespace ImageSharp.Tests.Common
public double B;
public Foo(int a, double b)
{
this.A = a;
this.B = b;
}
internal static Foo[] CreateArray(int size)
{
Foo[] result = new Foo[size];
for (int i = 0; i < size; i++)
{
result[i] = new Foo() { A = i, B = i };
result[i] = new Foo(i+1, i+1);
}
return result;
}
}
/// <summary>
/// sizeof(AlignedFoo) == sizeof(long)
/// </summary>
public struct AlignedFoo
{
public int A;
public int B;
static AlignedFoo()
{
Assert.Equal(sizeof(AlignedFoo), sizeof(long));
}
public AlignedFoo(int a, int b)
{
this.A = a;
this.B = b;
}
internal static AlignedFoo[] CreateArray(int size)
{
AlignedFoo[] result = new AlignedFoo[size];
for (int i = 0; i < size; i++)
{
result[i] = new AlignedFoo(i + 1, i + 1);
}
return result;
}
}
[Fact]
public void AsBytes()
{
Foo[] fooz = { new Foo(1, 2), new Foo(3, 4), new Foo(5, 6) };
using (PinnedBuffer<Foo> colorBuf = new PinnedBuffer<Foo>(fooz))
{
BufferPointer<Foo> orig = colorBuf.Slice(1);
BufferPointer<byte> asBytes = (BufferPointer < byte > )orig;
Assert.Equal(asBytes.Offset, sizeof(Foo));
Assert.Equal(orig.PointerAtOffset, asBytes.PointerAtOffset);
}
}
[Fact]
public void ConstructWithoutOffset()
@ -79,8 +131,58 @@ namespace ImageSharp.Tests.Common
}
}
[Theory]
[InlineData(4)]
[InlineData(1500)]
public void Clear(int count)
{
Foo[] array = Foo.CreateArray(count + 42);
int offset = 2;
fixed (Foo* p = array)
{
BufferPointer<Foo> ap = new BufferPointer<Foo>(array, p, offset);
// Act:
ap.Clear(count);
Assert.NotEqual(default(Foo), array[offset-1]);
Assert.Equal(default(Foo), array[offset]);
Assert.Equal(default(Foo), array[offset + count-1]);
Assert.NotEqual(default(Foo), array[offset + count]);
}
}
public class Copy
{
private static void AssertNotDefault<T>(T[] data, int idx)
where T : struct
{
Assert.NotEqual(default(T), data[idx]);
}
private static byte[] CreateTestBytes(int count)
{
byte[] result = new byte[count];
for (int i = 0; i < result.Length; i++)
{
result[i] = (byte)((i % 200) + 1);
}
return result;
}
private static int[] CreateTestInts(int count)
{
int[] result = new int[count];
for (int i = 0; i < result.Length; i++)
{
result[i] = i + 1;
}
return result;
}
[Theory]
[InlineData(4)]
[InlineData(1500)]
@ -92,35 +194,150 @@ namespace ImageSharp.Tests.Common
fixed (Foo* pSource = source)
fixed (Foo* pDest = dest)
{
BufferPointer<Foo> apSource = new BufferPointer<Foo>(source, pSource);
BufferPointer<Foo> apDest = new BufferPointer<Foo>(dest, pDest);
BufferPointer<Foo> apSource = new BufferPointer<Foo>(source, pSource, 1);
BufferPointer<Foo> apDest = new BufferPointer<Foo>(dest, pDest, 1);
BufferPointer.Copy(apSource, apDest, count);
BufferPointer.Copy(apSource, apDest, count-1);
}
Assert.Equal(source[0], dest[0]);
AssertNotDefault(source, 1);
AssertNotDefault(dest, 1);
Assert.NotEqual(source[0], dest[0]);
Assert.Equal(source[1], dest[1]);
Assert.Equal(source[2], dest[2]);
Assert.Equal(source[count-1], dest[count-1]);
Assert.NotEqual(source[count], dest[count]);
}
[Theory]
[InlineData(4)]
[InlineData(1500)]
public void GenericToOwnType_Aligned(int count)
{
AlignedFoo[] source = AlignedFoo.CreateArray(count + 2);
AlignedFoo[] dest = new AlignedFoo[count + 5];
fixed (AlignedFoo* pSource = source)
fixed (AlignedFoo* pDest = dest)
{
BufferPointer<AlignedFoo> apSource = new BufferPointer<AlignedFoo>(source, pSource, 1);
BufferPointer<AlignedFoo> apDest = new BufferPointer<AlignedFoo>(dest, pDest, 1);
BufferPointer.Copy(apSource, apDest, count - 1);
}
AssertNotDefault(source, 1);
AssertNotDefault(dest, 1);
Assert.NotEqual(source[0], dest[0]);
Assert.Equal(source[1], dest[1]);
Assert.Equal(source[2], dest[2]);
Assert.Equal(source[count - 1], dest[count - 1]);
Assert.NotEqual(source[count], dest[count]);
}
[Theory]
[InlineData(4)]
[InlineData(1500)]
public void IntToInt(int count)
{
int[] source = CreateTestInts(count+2);
int[] dest = new int[count + 5];
fixed (int* pSource = source)
fixed (int* pDest = dest)
{
BufferPointer<int> apSource = new BufferPointer<int>(source, pSource, 1);
BufferPointer<int> apDest = new BufferPointer<int>(dest, pDest, 1);
BufferPointer.Copy(apSource, apDest, count -1);
}
AssertNotDefault(source, 1);
AssertNotDefault(dest, 1);
Assert.NotEqual(source[0], dest[0]);
Assert.Equal(source[1], dest[1]);
Assert.Equal(source[2], dest[2]);
Assert.Equal(source[count - 1], dest[count - 1]);
Assert.NotEqual(source[count], dest[count]);
}
[Theory]
[InlineData(4)]
[InlineData(1500)]
public void GenericToBytes(int count)
{
int destCount = count * sizeof(Foo);
Foo[] source = Foo.CreateArray(count + 2);
byte[] dest = new byte[destCount + sizeof(Foo) + 1];
Foo[] source = Foo.CreateArray(count+2);
byte[] dest = new byte[destCount + sizeof(Foo)*2];
fixed (Foo* pSource = source)
fixed (byte* pDest = dest)
{
BufferPointer<Foo> apSource = new BufferPointer<Foo>(source, pSource);
BufferPointer<Foo> apSource = new BufferPointer<Foo>(source, pSource, 1);
BufferPointer<byte> apDest = new BufferPointer<byte>(dest, pDest, sizeof(Foo));
BufferPointer.Copy(apSource, apDest, count - 1);
}
AssertNotDefault(source, 1);
Assert.False(ElementsAreEqual(source, dest, 0));
Assert.True(ElementsAreEqual(source, dest, 1));
Assert.True(ElementsAreEqual(source, dest, 2));
Assert.True(ElementsAreEqual(source, dest, count - 1));
Assert.False(ElementsAreEqual(source, dest, count));
}
[Theory]
[InlineData(4)]
[InlineData(1500)]
public void GenericToBytes_Aligned(int count)
{
int destCount = count * sizeof(Foo);
AlignedFoo[] source = AlignedFoo.CreateArray(count + 2);
byte[] dest = new byte[destCount + sizeof(AlignedFoo) * 2];
fixed (AlignedFoo* pSource = source)
fixed (byte* pDest = dest)
{
BufferPointer<AlignedFoo> apSource = new BufferPointer<AlignedFoo>(source, pSource, 1);
BufferPointer<byte> apDest = new BufferPointer<byte>(dest, pDest, sizeof(AlignedFoo));
BufferPointer.Copy(apSource, apDest, count - 1);
}
AssertNotDefault(source, 1);
Assert.False(ElementsAreEqual(source, dest, 0));
Assert.True(ElementsAreEqual(source, dest, 1));
Assert.True(ElementsAreEqual(source, dest, 2));
Assert.True(ElementsAreEqual(source, dest, count - 1));
Assert.False(ElementsAreEqual(source, dest, count));
}
[Theory]
[InlineData(4)]
[InlineData(1500)]
public void IntToBytes(int count)
{
int destCount = count * sizeof(int);
int[] source = CreateTestInts(count+2);
byte[] dest = new byte[destCount + sizeof(int) + 1];
fixed (int* pSource = source)
fixed (byte* pDest = dest)
{
BufferPointer<int> apSource = new BufferPointer<int>(source, pSource);
BufferPointer<byte> apDest = new BufferPointer<byte>(dest, pDest);
BufferPointer.Copy(apSource, apDest, count);
}
AssertNotDefault(source, 1);
Assert.True(ElementsAreEqual(source, dest, 0));
Assert.True(ElementsAreEqual(source, dest, count - 1));
Assert.False(ElementsAreEqual(source, dest, count));
@ -131,9 +348,9 @@ namespace ImageSharp.Tests.Common
[InlineData(1500)]
public void BytesToGeneric(int count)
{
int destCount = count * sizeof(Foo);
byte[] source = new byte[destCount + sizeof(Foo) + 1];
Foo[] dest = Foo.CreateArray(count + 2);
int srcCount = count * sizeof(Foo);
byte[] source = CreateTestBytes(srcCount);
Foo[] dest = new Foo[count + 2];
fixed(byte* pSource = source)
fixed (Foo* pDest = dest)
@ -144,12 +361,35 @@ namespace ImageSharp.Tests.Common
BufferPointer.Copy(apSource, apDest, count);
}
AssertNotDefault(source, sizeof(Foo) + 1);
AssertNotDefault(dest, 1);
Assert.True(ElementsAreEqual(dest, source, 0));
Assert.True(ElementsAreEqual(dest, source, 1));
Assert.True(ElementsAreEqual(dest, source, count - 1));
Assert.False(ElementsAreEqual(dest, source, count));
}
private static bool ElementsAreEqual(Foo[] array, byte[] rawArray, int index)
[Fact]
public void ColorToBytes()
{
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 (PinnedBuffer<byte> byteBuf = new PinnedBuffer<byte>(colors.Length*4))
{
BufferPointer.Copy<Color>(colorBuf, byteBuf, colorBuf.Count);
byte[] a = byteBuf.Array;
for (int i = 0; i < byteBuf.Count; i++)
{
Assert.Equal((byte)i, a[i]);
}
}
}
internal static bool ElementsAreEqual(Foo[] array, byte[] rawArray, int index)
{
fixed (Foo* pArray = array)
fixed (byte* pRaw = rawArray)
@ -162,6 +402,34 @@ namespace ImageSharp.Tests.Common
return val1.Equals(val2);
}
}
internal static bool ElementsAreEqual(AlignedFoo[] array, byte[] rawArray, int index)
{
fixed (AlignedFoo* pArray = array)
fixed (byte* pRaw = rawArray)
{
AlignedFoo* pCasted = (AlignedFoo*)pRaw;
AlignedFoo val1 = pArray[index];
AlignedFoo val2 = pCasted[index];
return val1.Equals(val2);
}
}
internal static bool ElementsAreEqual(int[] array, byte[] rawArray, int index)
{
fixed (int* pArray = array)
fixed (byte* pRaw = rawArray)
{
int* pCasted = (int*)pRaw;
int val1 = pArray[index];
int val2 = pCasted[index];
return val1.Equals(val2);
}
}
}
}
}

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

@ -47,6 +47,21 @@
}
}
[Theory]
[InlineData(42)]
[InlineData(1111)]
public void Clear(int count)
{
Foo[] a = { new Foo() { A = 1, B = 2 }, new Foo() { A = 3, B = 4 } };
using (PinnedBuffer<Foo> buffer = new PinnedBuffer<Foo>(a))
{
buffer.Clear();
Assert.Equal(default(Foo), a[0]);
Assert.Equal(default(Foo), a[1]);
}
}
[Fact]
public void Dispose()
{

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

@ -3,6 +3,7 @@
// Licensed under the Apache License, Version 2.0.
// </copyright>
// ReSharper disable InconsistentNaming
namespace ImageSharp.Tests
{
using System.Linq;
@ -22,28 +23,6 @@ namespace ImageSharp.Tests
Assert.True(pixels.Length >= 1024);
}
[Fact]
public void PixelDataPoolRentsEmptyArray()
{
for (int i = 16; i < 1024; i += 16)
{
Color[] pixels = PixelDataPool<Color>.Rent(i);
Assert.True(pixels.All(p => p == default(Color)));
PixelDataPool<Color>.Return(pixels);
}
for (int i = 16; i < 1024; i += 16)
{
Color[] pixels = PixelDataPool<Color>.Rent(i);
Assert.True(pixels.All(p => p == default(Color)));
PixelDataPool<Color>.Return(pixels);
}
}
[Fact]
public void PixelDataPoolDoesNotThrowWhenReturningNonPooled()
{
@ -54,23 +33,6 @@ namespace ImageSharp.Tests
Assert.True(pixels.Length >= 1024);
}
[Fact]
public void PixelDataPoolCleansRentedArray()
{
Color[] pixels = PixelDataPool<Color>.Rent(256);
for (int i = 0; i < pixels.Length; i++)
{
pixels[i] = Color.Azure;
}
Assert.True(pixels.All(p => p == Color.Azure));
PixelDataPool<Color>.Return(pixels);
Assert.True(pixels.All(p => p == default(Color)));
}
[Theory]
[InlineData(false)]
[InlineData(true)]

25
tests/ImageSharp.Tests/Formats/Jpg/JpegUtilityTestFixture.cs

@ -4,14 +4,13 @@
// </copyright>
using System.Text;
using ImageSharp.Formats;
using Xunit.Abstractions;
// ReSharper disable InconsistentNaming
namespace ImageSharp.Tests
{
using System;
using System.Collections.Generic;
using System.Diagnostics;
using ImageSharp.Formats.Jpg;
@ -99,28 +98,6 @@ namespace ImageSharp.Tests
this.Output.WriteLine(bld.ToString());
}
internal struct ApproximateFloatComparer : IEqualityComparer<float>
{
private readonly float Eps;
public ApproximateFloatComparer(float eps = 1f)
{
this.Eps = eps;
}
public bool Equals(float x, float y)
{
float d = x - y;
return d > -this.Eps && d < this.Eps;
}
public int GetHashCode(float obj)
{
throw new InvalidOperationException();
}
}
protected void Print(string msg)
{
Debug.WriteLine(msg);

44
tests/ImageSharp.Tests/Image/PixelAccessorTests.cs

@ -130,16 +130,7 @@ namespace ImageSharp.Tests
CopyFromZYX(image);
}
}
[Fact]
public void CopyFromZYXOptimized()
{
using (Image image = new Image(1, 1))
{
CopyFromZYX(image);
}
}
[Fact]
public void CopyFromZYXW()
{
@ -148,16 +139,7 @@ namespace ImageSharp.Tests
CopyFromZYXW(image);
}
}
[Fact]
public void CopyFromZYXWOptimized()
{
using (Image image = new Image(1, 1))
{
CopyFromZYXW(image);
}
}
[Fact]
public void CopyToZYX()
{
@ -166,16 +148,7 @@ namespace ImageSharp.Tests
CopyToZYX(image);
}
}
[Fact]
public void CopyToZYXOptimized()
{
using (Image image = new Image(1, 1))
{
CopyToZYX(image);
}
}
[Fact]
public void CopyToZYXW()
{
@ -184,16 +157,7 @@ namespace ImageSharp.Tests
CopyToZYXW(image);
}
}
[Fact]
public void CopyToZYXWOptimized()
{
using (Image image = new Image(1, 1))
{
CopyToZYXW(image);
}
}
private static void CopyFromZYX<TColor>(Image<TColor> image)
where TColor : struct, IPixel<TColor>
{

38
tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs

@ -0,0 +1,38 @@
namespace ImageSharp.Tests
{
using System;
using System.Collections.Generic;
using System.Numerics;
internal struct ApproximateFloatComparer : IEqualityComparer<float>, IEqualityComparer<Vector4>
{
private readonly float Eps;
public ApproximateFloatComparer(float eps = 1f)
{
this.Eps = eps;
}
public bool Equals(float x, float y)
{
float d = x - y;
return d > -this.Eps && d < this.Eps;
}
public int GetHashCode(float obj)
{
throw new InvalidOperationException();
}
public bool Equals(Vector4 a, Vector4 b)
{
return this.Equals(a.X, b.X) && this.Equals(a.Y, b.Y) && this.Equals(a.Z, b.Z) && this.Equals(a.W, b.W);
}
public int GetHashCode(Vector4 obj)
{
throw new InvalidOperationException();
}
}
}

3
theme/index.cshtml

@ -0,0 +1,3 @@
Title: Home
---
Welcome to the documentation for ImageSharp
Loading…
Cancel
Save