diff --git a/.gitignore b/.gitignore index 61c78d0c8..fb8af2320 100644 --- a/.gitignore +++ b/.gitignore @@ -215,4 +215,5 @@ artifacts/ *.csproj.bak #CodeCoverage -**/CodeCoverage/* \ No newline at end of file +**/CodeCoverage/* +docs/ diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 000000000..ae6dd5f6b --- /dev/null +++ b/codecov.yml @@ -0,0 +1,4 @@ +ignore: + "src/ImageSharp/Common/Helpers/DebugGuard.cs" + + \ No newline at end of file diff --git a/config.wyam b/config.wyam new file mode 100644 index 000000000..3a4b64c54 --- /dev/null +++ b/config.wyam @@ -0,0 +1,4 @@ +#recipe Docs +Settings[Keys.Host] = "imagesharp.org"; +Settings[Keys.Title] = "Image Sharp"; +FileSystem.OutputPath = "./docs"; \ No newline at end of file diff --git a/input/about.md b/input/about.md new file mode 100644 index 000000000..42739928a --- /dev/null +++ b/input/about.md @@ -0,0 +1,3 @@ +Title: About This Project +--- +This project is awesome! \ No newline at end of file diff --git a/src/ImageSharp/Colors/Color.BulkOperations.cs b/src/ImageSharp/Colors/Color.BulkOperations.cs new file mode 100644 index 000000000..5c040e04c --- /dev/null +++ b/src/ImageSharp/Colors/Color.BulkOperations.cs @@ -0,0 +1,249 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Numerics; + using System.Runtime.CompilerServices; + using System.Runtime.InteropServices; + + /// + /// Conains the definition of + /// + public partial struct Color + { + /// + /// implementation optimized for . + /// + internal class BulkOperations : BulkPixelOperations + { + /// + /// SIMD optimized bulk implementation of + /// that works only with `count` divisible by . + /// + /// The to the source colors. + /// The to the dstination vectors. + /// The number of pixels to convert. + /// + /// Implementation adapted from: + /// + /// http://stackoverflow.com/a/5362789 + /// + /// TODO: We can replace this implementation in the future using new Vector API-s: + /// + /// https://github.com/dotnet/corefx/issues/15957 + /// + /// + internal static unsafe void ToVector4SimdAligned( + BufferPointer sourceColors, + BufferPointer destVectors, + int count) + { + int vecSize = Vector.Count; + + DebugGuard.IsTrue( + count % vecSize == 0, + nameof(count), + "Argument 'count' should divisible by Vector.Count!"); + + Vector bVec = new Vector(256.0f / 255.0f); + Vector magicFloat = new Vector(32768.0f); + Vector magicInt = new Vector(1191182336); // reinterpreded value of 32768.0f + Vector mask = new Vector(255); + + int unpackedRawCount = count * 4; + + uint* src = (uint*)sourceColors.PointerAtOffset; + uint* srcEnd = src + count; + + using (PinnedBuffer tempBuf = new PinnedBuffer( + unpackedRawCount + Vector.Count)) + { + uint* tPtr = (uint*)tempBuf.Pointer; + uint[] temp = tempBuf.Array; + float[] fTemp = Unsafe.As(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 vi = new Vector(temp, i); + + vi &= mask; + vi |= magicInt; + + Vector vf = Vector.AsVectorSingle(vi); + vf = (vf - magicFloat) * bVec; + vf.CopyTo(fTemp, i); + } + + BufferPointer.Copy(tempBuf, (BufferPointer)destVectors, unpackedRawCount); + } + } + + /// + internal override void ToVector4(BufferPointer sourceColors, BufferPointer destVectors, int count) + { + if (count < 256) + { + // Doesn't worth to bother with SIMD: + base.ToVector4(sourceColors, destVectors, count); + return; + } + + int remainder = count % Vector.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); + } + } + + /// + internal override unsafe void PackFromXyzBytes(BufferPointer sourceBytes, BufferPointer 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; + } + } + + /// + internal override unsafe void ToXyzBytes(BufferPointer sourceColors, BufferPointer 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; + } + } + + /// + internal override void PackFromXyzwBytes(BufferPointer sourceBytes, BufferPointer destColors, int count) + { + BufferPointer.Copy(sourceBytes, destColors, count); + } + + /// + internal override void ToXyzwBytes(BufferPointer sourceColors, BufferPointer destBytes, int count) + { + BufferPointer.Copy(sourceColors, destBytes, count); + } + + /// + internal override unsafe void PackFromZyxBytes(BufferPointer sourceBytes, BufferPointer 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; + } + } + + /// + internal override unsafe void ToZyxBytes(BufferPointer sourceColors, BufferPointer 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; + } + } + + /// + internal override unsafe void PackFromZyxwBytes(BufferPointer sourceBytes, BufferPointer 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; + } + } + + /// + internal override unsafe void ToZyxwBytes(BufferPointer sourceColors, BufferPointer 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; + } + } + + /// + /// Value type to store -s unpacked into multiple -s. + /// + 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; + } + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Colors/Color.cs b/src/ImageSharp/Colors/Color.cs index 1ad6f9a64..597730937 100644 --- a/src/ImageSharp/Colors/Color.cs +++ b/src/ImageSharp/Colors/Color.cs @@ -246,7 +246,7 @@ namespace ImageSharp } /// - public BulkPixelOperations CreateBulkOperations() => new BulkPixelOperations(); + public BulkPixelOperations CreateBulkOperations() => new Color.BulkOperations(); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/ImageSharp/Common/Helpers/DebugGuard.cs b/src/ImageSharp/Common/Helpers/DebugGuard.cs index 0ca6f0912..c1fa46191 100644 --- a/src/ImageSharp/Common/Helpers/DebugGuard.cs +++ b/src/ImageSharp/Common/Helpers/DebugGuard.cs @@ -29,5 +29,135 @@ namespace ImageSharp throw new ArgumentNullException(parameterName); } } + + /// + /// Verifies that the specified value is less than a maximum value + /// and throws an exception if it is not. + /// + /// The target value, which should be validated. + /// The maximum value. + /// The name of the parameter that is to be checked. + /// The type of the value. + /// + /// is greater than the maximum value. + /// + [Conditional("DEBUG")] + public static void MustBeLessThan(TValue value, TValue max, string parameterName) + where TValue : IComparable + { + if (value.CompareTo(max) >= 0) + { + throw new ArgumentOutOfRangeException(parameterName, $"Value must be less than {max}."); + } + } + + /// + /// Verifies that the specified value is less than or equal to a maximum value + /// and throws an exception if it is not. + /// + /// The target value, which should be validated. + /// The maximum value. + /// The name of the parameter that is to be checked. + /// The type of the value. + /// + /// is greater than the maximum value. + /// + [Conditional("DEBUG")] + public static void MustBeLessThanOrEqualTo(TValue value, TValue max, string parameterName) + where TValue : IComparable + { + if (value.CompareTo(max) > 0) + { + throw new ArgumentOutOfRangeException(parameterName, $"Value must be less than or equal to {max}."); + } + } + + /// + /// Verifies that the specified value is greater than a minimum value + /// and throws an exception if it is not. + /// + /// The target value, which should be validated. + /// The minimum value. + /// The name of the parameter that is to be checked. + /// The type of the value. + /// + /// is less than the minimum value. + /// + [Conditional("DEBUG")] + public static void MustBeGreaterThan(TValue value, TValue min, string parameterName) + where TValue : IComparable + { + if (value.CompareTo(min) <= 0) + { + throw new ArgumentOutOfRangeException( + parameterName, + $"Value must be greater than {min}."); + } + } + + /// + /// Verifies that the specified value is greater than or equal to a minimum value + /// and throws an exception if it is not. + /// + /// The target value, which should be validated. + /// The minimum value. + /// The name of the parameter that is to be checked. + /// The type of the value. + /// + /// is less than the minimum value. + /// + [Conditional("DEBUG")] + public static void MustBeGreaterThanOrEqualTo(TValue value, TValue min, string parameterName) + where TValue : IComparable + { + if (value.CompareTo(min) < 0) + { + throw new ArgumentOutOfRangeException(parameterName, $"Value must be greater than or equal to {min}."); + } + } + + /// + /// Verifies, that the method parameter with specified target value is true + /// and throws an exception if it is found to be so. + /// + /// + /// The target value, which cannot be false. + /// + /// + /// The name of the parameter that is to be checked. + /// + /// + /// The error message, if any to add to the exception. + /// + /// + /// is false + /// + [Conditional("DEBUG")] + public static void IsTrue(bool target, string parameterName, string message) + { + if (!target) + { + throw new ArgumentException(message, parameterName); + } + } + + /// + /// Verifies, that the method parameter with specified target value is false + /// and throws an exception if it is found to be so. + /// + /// The target value, which cannot be true. + /// The name of the parameter that is to be checked. + /// The error message, if any to add to the exception. + /// + /// is true + /// + [Conditional("DEBUG")] + public static void IsFalse(bool target, string parameterName, string message) + { + if (target) + { + throw new ArgumentException(message, parameterName); + } + } } } \ No newline at end of file diff --git a/src/ImageSharp/Common/Memory/BufferPointer.cs b/src/ImageSharp/Common/Memory/BufferPointer.cs index c80e22e21..523889611 100644 --- a/src/ImageSharp/Common/Memory/BufferPointer.cs +++ b/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; /// /// Utility methods for @@ -13,17 +16,9 @@ namespace ImageSharp internal static class BufferPointer { /// - /// Gets a to the beginning of the raw data in 'buffer'. + /// It's worth to use Marshal.Copy() or Buffer.BlockCopy() over this size. /// - /// The element type - /// The input - /// The - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static unsafe BufferPointer Slice(this PinnedBuffer buffer) - where T : struct - { - return new BufferPointer(buffer.Array, (void*)buffer.Pointer); - } + private const int ByteCountThreshold = 1024; /// /// Copy 'count' number of elements of the same type from 'source' to 'dest' @@ -33,10 +28,10 @@ namespace ImageSharp /// The destination . /// The number of elements to copy [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static unsafe void Copy(BufferPointer source, BufferPointer destination, int count) + public static void Copy(BufferPointer source, BufferPointer destination, int count) where T : struct { - Unsafe.CopyBlock((void*)source.PointerAtOffset, (void*)destination.PointerAtOffset, USizeOf(count)); + CopyImpl(source, destination, count); } /// @@ -47,10 +42,10 @@ namespace ImageSharp /// The destination buffer. /// The number of elements to copy from 'source' [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static unsafe void Copy(BufferPointer source, BufferPointer destination, int countInSource) + public static void Copy(BufferPointer source, BufferPointer destination, int countInSource) where T : struct { - Unsafe.CopyBlock((void*)source.PointerAtOffset, (void*)destination.PointerAtOffset, USizeOf(countInSource)); + CopyImpl(source, destination, countInSource); } /// @@ -64,7 +59,16 @@ namespace ImageSharp public static unsafe void Copy(BufferPointer source, BufferPointer destination, int countInDest) where T : struct { - Unsafe.CopyBlock((void*)source.PointerAtOffset, (void*)destination.PointerAtOffset, USizeOf(countInDest)); + int byteCount = SizeOf(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); + } } /// @@ -87,5 +91,39 @@ namespace ImageSharp public static uint USizeOf(int count) where T : struct => (uint)SizeOf(count); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static unsafe void CopyImpl(BufferPointer source, BufferPointer destination, int count) + where T : struct + where TDest : struct + { + int byteCount = SizeOf(count); + + if (byteCount > ByteCountThreshold) + { + if (Unsafe.SizeOf() == sizeof(long)) + { + Marshal.Copy(Unsafe.As(source.Array), source.Offset, destination.PointerAtOffset, count); + return; + } + else if (Unsafe.SizeOf() == sizeof(int)) + { + Marshal.Copy(Unsafe.As(source.Array), source.Offset, destination.PointerAtOffset, count); + return; + } + else if (Unsafe.SizeOf() == sizeof(short)) + { + Marshal.Copy(Unsafe.As(source.Array), source.Offset, destination.PointerAtOffset, count); + return; + } + else if (Unsafe.SizeOf() == sizeof(byte)) + { + Marshal.Copy(Unsafe.As(source.Array), source.Offset, destination.PointerAtOffset, count); + return; + } + } + + Unsafe.CopyBlock((void*)destination.PointerAtOffset, (void*)source.PointerAtOffset, (uint)byteCount); + } } } \ No newline at end of file diff --git a/src/ImageSharp/Common/Memory/BufferPointer{T}.cs b/src/ImageSharp/Common/Memory/BufferPointer{T}.cs index fff4e513e..441f6b8ce 100644 --- a/src/ImageSharp/Common/Memory/BufferPointer{T}.cs +++ b/src/ImageSharp/Common/Memory/BufferPointer{T}.cs @@ -63,6 +63,11 @@ namespace ImageSharp /// public int Offset { get; private set; } + /// + /// Gets the offset inside in bytes. + /// + public int ByteOffset => this.Offset * Unsafe.SizeOf(); + /// /// Gets the pointer to the offseted array position /// @@ -79,7 +84,7 @@ namespace ImageSharp } /// - /// Convertes instance to a raw 'byte*' pointer + /// Converts instance to a raw 'byte*' pointer /// /// The to convert [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -88,6 +93,21 @@ namespace ImageSharp return (byte*)bufferPointer.PointerAtOffset; } + /// + /// Converts instance to + /// setting it's and to correct values. + /// + /// The to convert + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator BufferPointer(BufferPointer source) + { + BufferPointer result = default(BufferPointer); + result.Array = Unsafe.As(source.Array); + result.Offset = source.Offset * Unsafe.SizeOf(); + result.PointerAtOffset = source.PointerAtOffset; + return result; + } + /// /// Forms a slice out of the given BufferPointer, beginning at 'offset'. /// @@ -102,5 +122,22 @@ namespace ImageSharp result.PointerAtOffset = this.PointerAtOffset + (Unsafe.SizeOf() * offset); return result; } + + /// + /// Clears `count` elements beginning from the pointed position. + /// + /// The number of elements to clear + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Clear(int count) + { + if (count < 256) + { + Unsafe.InitBlock((void*)this.PointerAtOffset, 0, BufferPointer.USizeOf(count)); + } + else + { + System.Array.Clear(this.Array, this.Offset, count); + } + } } } \ No newline at end of file diff --git a/src/ImageSharp/Common/Memory/PinnedBuffer{T}.cs b/src/ImageSharp/Common/Memory/PinnedBuffer{T}.cs index 04217a012..2d3d44dda 100644 --- a/src/ImageSharp/Common/Memory/PinnedBuffer{T}.cs +++ b/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; /// @@ -23,7 +24,8 @@ namespace ImageSharp private GCHandle handle; /// - /// A value indicating whether this instance should return the array to the pool. + /// A value indicating wheter should be returned to + /// when disposing this instance. /// 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 /// public IntPtr Pointer { get; private set; } + /// + /// Converts to an . + /// + /// The to convert. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator BufferPointer(PinnedBuffer buffer) + { + return buffer.Slice(); + } + + /// + /// Gets a to the beginning of the raw data of the buffer. + /// + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe BufferPointer Slice() + { + return new BufferPointer(this.Array, (void*)this.Pointer); + } + + /// + /// Gets a to an offseted position inside the buffer. + /// + /// The offset + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe BufferPointer Slice(int offset) + { + return new BufferPointer(this.Array, (void*)this.Pointer, offset); + } + /// /// Disposes the instance by unpinning the array, and returning the pooled buffer when necessary. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Dispose() { if (this.IsDisposedOrLostArrayOwnership) @@ -113,6 +149,7 @@ namespace ImageSharp PixelDataPool.Return(this.Array); } + this.isPoolingOwner = false; this.Array = null; this.Count = 0; @@ -124,6 +161,7 @@ namespace ImageSharp /// If is rented, it's the callers responsibility to return it to it's pool. (Most likely ) /// /// The unpinned + [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; } + /// + /// Clears the buffer, filling elements between 0 and -1 with default(T) + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Clear() + { + this.Slice().Clear(this.Count); + } + /// /// Pins . /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] private void Pin() { this.handle = GCHandle.Alloc(this.Array, GCHandleType.Pinned); @@ -150,6 +199,7 @@ namespace ImageSharp /// /// Unpins . /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] private void UnPin() { if (this.Pointer == IntPtr.Zero || !this.handle.IsAllocated) diff --git a/src/ImageSharp/Common/Memory/PixelDataPool{T}.cs b/src/ImageSharp/Common/Memory/PixelDataPool{T}.cs index a97d17fdb..dcd031f6e 100644 --- a/src/ImageSharp/Common/Memory/PixelDataPool{T}.cs +++ b/src/ImageSharp/Common/Memory/PixelDataPool{T}.cs @@ -9,15 +9,14 @@ namespace ImageSharp using System.Buffers; /// - /// Provides a resource pool that enables reusing instances of value type arrays . - /// will always return arrays initialized with 'default(T)' + /// Provides a resource pool that enables reusing instances of value type arrays for image data . /// /// The value type. - public static class PixelDataPool + public class PixelDataPool where T : struct { /// - /// The used to pool data. + /// The which is not kept clean. /// private static readonly ArrayPool ArrayPool = ArrayPool.Create(CalculateMaxArrayLength(), 50); @@ -37,11 +36,11 @@ namespace ImageSharp /// The array to return to the buffer pool. public static void Return(T[] array) { - ArrayPool.Return(array, true); + ArrayPool.Return(array); } /// - /// Heuristically calculates a reasonable maxArrayLength value for the backing . + /// Heuristically calculates a reasonable maxArrayLength value for the backing . /// /// The maxArrayLength value internal static int CalculateMaxArrayLength() diff --git a/src/ImageSharp/Image.cs b/src/ImageSharp/Image.cs index af31eff79..8bfd8ee1a 100644 --- a/src/ImageSharp/Image.cs +++ b/src/ImageSharp/Image.cs @@ -223,17 +223,5 @@ namespace ImageSharp : base(other) { } - - /// - public override PixelAccessor Lock() - { - return new PixelAccessor(this); - } - - /// - internal override ImageFrame ToFrame() - { - return new ImageFrame(this); - } } } diff --git a/src/ImageSharp/Image/ImageBase{TColor}.cs b/src/ImageSharp/Image/ImageBase{TColor}.cs index 2badc008a..878ba09b3 100644 --- a/src/ImageSharp/Image/ImageBase{TColor}.cs +++ b/src/ImageSharp/Image/ImageBase{TColor}.cs @@ -60,6 +60,7 @@ namespace ImageSharp { this.Configuration = configuration ?? Configuration.Default; this.InitPixels(width, height); + this.ClearPixels(); } /// @@ -150,7 +151,7 @@ namespace ImageSharp } /// - public virtual PixelAccessor Lock() + public PixelAccessor Lock() { return new PixelAccessor(this); } @@ -232,5 +233,13 @@ namespace ImageSharp PixelDataPool.Return(this.pixelBuffer); this.pixelBuffer = null; } + + /// + /// Clears the pixel array. + /// + private void ClearPixels() + { + Array.Clear(this.pixelBuffer, 0, this.Width * this.Height); + } } } \ No newline at end of file diff --git a/src/ImageSharp/Image/PixelAccessor{TColor}.cs b/src/ImageSharp/Image/PixelAccessor{TColor}.cs index e104b8ae7..a10676565 100644 --- a/src/ImageSharp/Image/PixelAccessor{TColor}.cs +++ b/src/ImageSharp/Image/PixelAccessor{TColor}.cs @@ -15,7 +15,7 @@ namespace ImageSharp /// Provides per-pixel access to generic pixels. /// /// The pixel format. - public unsafe class PixelAccessor : IDisposable + public sealed unsafe class PixelAccessor : IDisposable where TColor : struct, IPixel { /// @@ -91,7 +91,7 @@ namespace ImageSharp /// /// Gets the pixel buffer array. /// - public TColor[] PixelBuffer => this.pixelBuffer.Array; + public TColor[] PixelArray => this.pixelBuffer.Array; /// /// Gets the pointer to the pixel buffer. @@ -123,6 +123,8 @@ namespace ImageSharp /// public ParallelOptions ParallelOptions { get; } + private static BulkPixelOperations Operations => BulkPixelOperations.Instance; + /// /// Gets or sets the pixel at the specified position. /// @@ -236,6 +238,17 @@ namespace ImageSharp Unsafe.InitBlock(this.pixelsBase, 0, (uint)(this.RowStride * this.Height)); } + /// + /// Gets a to the row 'y' beginning from the pixel at 'x'. + /// + /// The x coordinate + /// The y coordinate + /// The + internal BufferPointer GetRowPointer(int x, int y) + { + return this.pixelBuffer.Slice((y * this.Width) + x); + } + /// /// Sets the pixel buffer in an unsafe manner. This should not be used unless you know what its doing!!! /// @@ -270,24 +283,15 @@ namespace ImageSharp /// The target row index. /// The width. /// The height. - protected virtual void CopyFromZyx(PixelArea area, int targetX, int targetY, int width, int height) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void CopyFromZyx(PixelArea area, int targetX, int targetY, int width, int height) { - TColor packed = default(TColor); - int size = Unsafe.SizeOf(); - 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 source = area.GetRowPointer(y); + BufferPointer destination = this.GetRowPointer(targetX, targetY + y); - source += 3; - destination += size; - } + Operations.PackFromZyxBytes(source, destination, width); } } @@ -299,24 +303,15 @@ namespace ImageSharp /// The target row index. /// The width. /// The height. - protected virtual void CopyFromZyxw(PixelArea area, int targetX, int targetY, int width, int height) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void CopyFromZyxw(PixelArea area, int targetX, int targetY, int width, int height) { - TColor packed = default(TColor); - int size = Unsafe.SizeOf(); - 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 source = area.GetRowPointer(y); + BufferPointer destination = this.GetRowPointer(targetX, targetY + y); - source += 4; - destination += size; - } + Operations.PackFromZyxwBytes(source, destination, width); } } @@ -328,24 +323,15 @@ namespace ImageSharp /// The target row index. /// The width. /// The height. - protected virtual void CopyFromXyz(PixelArea area, int targetX, int targetY, int width, int height) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void CopyFromXyz(PixelArea area, int targetX, int targetY, int width, int height) { - TColor packed = default(TColor); - int size = Unsafe.SizeOf(); - 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 source = area.GetRowPointer(y); + BufferPointer destination = this.GetRowPointer(targetX, targetY + y); - source += 3; - destination += size; - } + Operations.PackFromXyzBytes(source, destination, width); } } @@ -357,24 +343,14 @@ namespace ImageSharp /// The target row index. /// The width. /// The height. - protected virtual void CopyFromXyzw(PixelArea area, int targetX, int targetY, int width, int height) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void CopyFromXyzw(PixelArea area, int targetX, int targetY, int width, int height) { - TColor packed = default(TColor); - int size = Unsafe.SizeOf(); - 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 source = area.GetRowPointer(y); + BufferPointer destination = this.GetRowPointer(targetX, targetY + y); + Operations.PackFromXyzwBytes(source, destination, width); } } @@ -386,16 +362,14 @@ namespace ImageSharp /// The source row index. /// The width. /// The height. - protected virtual void CopyToZyx(PixelArea area, int sourceX, int sourceY, int width, int height) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void CopyToZyx(PixelArea 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 source = this.GetRowPointer(sourceX, sourceY + y); + BufferPointer destination = area.GetRowPointer(y); + Operations.ToZyxBytes(source, destination, width); } } @@ -407,16 +381,14 @@ namespace ImageSharp /// The source row index. /// The width. /// The height. - protected virtual void CopyToZyxw(PixelArea area, int sourceX, int sourceY, int width, int height) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void CopyToZyxw(PixelArea 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 source = this.GetRowPointer(sourceX, sourceY + y); + BufferPointer destination = area.GetRowPointer(y); + Operations.ToZyxwBytes(source, destination, width); } } @@ -428,16 +400,14 @@ namespace ImageSharp /// The source row index. /// The width. /// The height. - protected virtual void CopyToXyz(PixelArea area, int sourceX, int sourceY, int width, int height) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void CopyToXyz(PixelArea 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 source = this.GetRowPointer(sourceX, sourceY + y); + BufferPointer destination = area.GetRowPointer(y); + Operations.ToXyzBytes(source, destination, width); } } @@ -449,32 +419,17 @@ namespace ImageSharp /// The source row index. /// The width. /// The height. - protected virtual void CopyToXyzw(PixelArea area, int sourceX, int sourceY, int width, int height) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void CopyToXyzw(PixelArea 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 source = this.GetRowPointer(sourceX, sourceY + y); + BufferPointer destination = area.GetRowPointer(y); + Operations.ToXyzwBytes(source, destination, width); } } - /// - /// Gets the pointer at the specified row. - /// - /// The column index. - /// The row index. - /// - /// The . - /// - protected byte* GetRowPointer(int x, int y) - { - return this.pixelsBase + (((y * this.Width) + x) * Unsafe.SizeOf()); - } - private void SetPixelBufferUnsafe(int width, int height, TColor[] pixels) { this.SetPixelBufferUnsafe(width, height, new PinnedBuffer(width * height, pixels)); diff --git a/src/ImageSharp/Image/PixelArea{TColor}.cs b/src/ImageSharp/Image/PixelArea{TColor}.cs index c54de12d6..be6debba2 100644 --- a/src/ImageSharp/Image/PixelArea{TColor}.cs +++ b/src/ImageSharp/Image/PixelArea{TColor}.cs @@ -203,6 +203,16 @@ namespace ImageSharp Unsafe.InitBlock(this.PixelBase, 0, (uint)(this.RowStride * this.Height)); } + /// + /// Gets a to the row y. + /// + /// The y coordinate + /// The + internal BufferPointer GetRowPointer(int y) + { + return this.byteBuffer.Slice(y * this.RowStride); + } + /// /// Gets component count for the given order. /// diff --git a/src/ImageSharp/ImageFrame.cs b/src/ImageSharp/ImageFrame.cs deleted file mode 100644 index be654111d..000000000 --- a/src/ImageSharp/ImageFrame.cs +++ /dev/null @@ -1,52 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp -{ - using System.Diagnostics; - - /// - /// An optimized frame for the class. - /// - [DebuggerDisplay("ImageFrame: {Width}x{Height}")] - public sealed class ImageFrame : ImageFrame - { - /// - /// Initializes a new instance of the class. - /// - /// The width of the image in pixels. - /// The height of the image in pixels. - /// - /// The configuration providing initialization code which allows extending the library. - /// - public ImageFrame(int width, int height, Configuration configuration = null) - : base(width, height, configuration) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The image to create the frame from. - /// - public ImageFrame(ImageBase image) - : base(image) - { - } - - /// - public override PixelAccessor Lock() - { - return new PixelAccessor(this); - } - - /// - internal override ImageFrame Clone() - { - return new ImageFrame(this); - } - } -} diff --git a/src/ImageSharp/PixelAccessor.cs b/src/ImageSharp/PixelAccessor.cs deleted file mode 100644 index 7827e7b47..000000000 --- a/src/ImageSharp/PixelAccessor.cs +++ /dev/null @@ -1,153 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp -{ - using System.Runtime.CompilerServices; - - /// - /// An optimized pixel accessor for the class. - /// - public sealed unsafe class PixelAccessor : PixelAccessor - { - /// - /// Initializes a new instance of the class. - /// - /// The image to provide pixel access for. - public PixelAccessor(ImageBase image) - : base(image) - { - } - - /// - protected override void CopyFromXyzw(PixelArea 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); - } - } - - /// - protected override void CopyFromXyz(PixelArea 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; - } - } - } - - /// - protected override void CopyFromZyx(PixelArea 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; - } - } - } - - /// - protected override void CopyFromZyxw(PixelArea 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; - } - } - } - - /// - protected override void CopyToZyx(PixelArea 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; - } - } - } - - /// - protected override void CopyToXyz(PixelArea 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; - } - } - } - - /// - protected override void CopyToZyxw(PixelArea 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; - } - } - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/PackFromXyzw.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/PackFromXyzw.cs new file mode 100644 index 000000000..1c541d28b --- /dev/null +++ b/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 + where TColor : struct, IPixel + { + private PinnedBuffer destination; + + private PinnedBuffer source; + + [Params(16, 128, 1024)] + public int Count { get; set; } + + [Setup] + public void Setup() + { + this.destination = new PinnedBuffer(this.Count); + this.source = new PinnedBuffer(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().PackFromXyzwBytes(this.source, this.destination, this.Count); + } + + [Benchmark] + public void OptimizedBulk() + { + BulkPixelOperations.Instance.PackFromXyzwBytes(this.source, this.destination, this.Count); + } + } + + public class PackFromXyzw_Color : PackFromXyzw + { + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/PixelAccessorVirtualCopy.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/PixelAccessorVirtualCopy.cs deleted file mode 100644 index 694a26f3d..000000000 --- a/tests/ImageSharp.Benchmarks/Color/Bulk/PixelAccessorVirtualCopy.cs +++ /dev/null @@ -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; - - /// - /// Benchmark to measure the effect of using virtual bulk-copy calls inside PixelAccessor methods - /// - public unsafe class PixelAccessorVirtualCopy - { - abstract class CopyExecutor - { - internal abstract void VirtualCopy(BufferPointer destination, BufferPointer source, int count); - } - - class UnsafeCopyExecutor : CopyExecutor - { - [MethodImpl(MethodImplOptions.NoInlining)] - internal override unsafe void VirtualCopy(BufferPointer destination, BufferPointer source, int count) - { - Unsafe.CopyBlock((void*)destination.PointerAtOffset, (void*)source.PointerAtOffset, (uint)count*4); - } - } - - private PixelAccessor pixelAccessor; - - private PixelArea 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(this.Width, this.Height); - this.area = new PixelArea(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 source = this.GetAreaRow(y); - BufferPointer 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 source = this.GetAreaRow(y); - BufferPointer 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()); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private BufferPointer GetPixelAccessorRow(int x, int y) - { - return new BufferPointer( - this.pixelAccessor.PixelBuffer, - (void*)this.pixelAccessor.DataPointer, - (y * this.pixelAccessor.Width) + x - ); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private BufferPointer GetAreaRow(int y) - { - return new BufferPointer(this.area.Bytes, this.area.PixelBase, y * this.area.RowStride); - } - } -} diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4.cs new file mode 100644 index 000000000..b48eaa35a --- /dev/null +++ b/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 + where TColor : struct, IPixel + { + private PinnedBuffer source; + + private PinnedBuffer destination; + + [Params(64, 300, 1024)] + public int Count { get; set; } + + [Setup] + public void Setup() + { + this.source = new PinnedBuffer(this.Count); + this.destination = new PinnedBuffer(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().ToVector4(this.source, this.destination, this.Count); + } + + [Benchmark] + public void OptimizedBulk() + { + BulkPixelOperations.Instance.ToVector4(this.source, this.destination, this.Count); + } + } + + public class ToVector4_Color : ToVector4 + { + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/ToXyz.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/ToXyz.cs new file mode 100644 index 000000000..bc59dba4e --- /dev/null +++ b/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 + where TColor : struct, IPixel + { + private PinnedBuffer source; + + private PinnedBuffer destination; + + [Params(16, 128, 1024)] + public int Count { get; set; } + + [Setup] + public void Setup() + { + this.source = new PinnedBuffer(this.Count); + this.destination = new PinnedBuffer(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().ToXyzBytes(this.source, this.destination, this.Count); + } + + [Benchmark] + public void OptimizedBulk() + { + BulkPixelOperations.Instance.ToXyzBytes(this.source, this.destination, this.Count); + } + } + + public class ToXyz_Color : ToXyz + { + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/ToXyzw.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/ToXyzw.cs new file mode 100644 index 000000000..a4ec6f6dc --- /dev/null +++ b/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 + where TColor : struct, IPixel + { + private PinnedBuffer source; + + private PinnedBuffer destination; + + [Params(16, 128, 1024)] + public int Count { get; set; } + + [Setup] + public void Setup() + { + this.source = new PinnedBuffer(this.Count); + this.destination = new PinnedBuffer(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().ToXyzwBytes(this.source, this.destination, this.Count); + } + + [Benchmark] + public void OptimizedBulk() + { + BulkPixelOperations.Instance.ToXyzwBytes(this.source, this.destination, this.Count); + } + } + + public class ToXyzw_Color : ToXyzw + { + } + + public class ToXyzw_Argb : ToXyzw + { + } +} diff --git a/tests/ImageSharp.Benchmarks/General/ClearBuffer.cs b/tests/ImageSharp.Benchmarks/General/ClearBuffer.cs new file mode 100644 index 000000000..9aa836de5 --- /dev/null +++ b/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 buffer; + + [Params(32, 128, 512)] + public int Count { get; set; } + + [Setup] + public void Setup() + { + this.buffer = new PinnedBuffer(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)); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Image/EncodePng.cs b/tests/ImageSharp.Benchmarks/Image/EncodePng.cs index dd1882c80..0bb0e922c 100644 --- a/tests/ImageSharp.Benchmarks/Image/EncodePng.cs +++ b/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 quantizer = this.UseOctreeQuantizer + ? (Quantizer) + new OctreeQuantizer() + : new PaletteQuantizer(); + + PngEncoderOptions options = new PngEncoderOptions() { Quantizer = quantizer }; + this.bmpCore.SaveAsPng(memoryStream, options); } } } diff --git a/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj b/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj index a1ae6bb0d..8837f8733 100644 --- a/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj +++ b/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj @@ -206,12 +206,12 @@ - - Benchmarks\PixelAccessorVirtualCopy.cs - Tests\Colors\BulkPixelOperationsTests.cs + + Tests\Common\BufferPointerTests.cs + Tests\Common\PinnedBufferTests.cs @@ -272,6 +272,9 @@ Tests\TestImages.cs + + Tests\TestUtilities\ApproximateFloatComparer.cs + Tests\TestUtilities\Attributes\ImageDataAttributeBase.cs @@ -340,7 +343,9 @@ - + + + diff --git a/tests/ImageSharp.Sandbox46/Program.cs b/tests/ImageSharp.Sandbox46/Program.cs index 4d6d15925..467663a53 100644 --- a/tests/ImageSharp.Sandbox46/Program.cs +++ b/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..."); diff --git a/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs b/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs index 80d5952a1..6621292ec 100644 --- a/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs +++ b/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 { + 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 ArraySizesData => new TheoryData { 7, 16, 1111 }; + + [Fact] + public void IsSpecialImplementation() + { + Assert.IsType(BulkPixelOperations.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 source = new PinnedBuffer(count)) + using (PinnedBuffer dest = new PinnedBuffer(count)) + { + this.Measure( + times, + () => + { + BulkPixelOperations.Instance.ToVector4(source, dest, count); + }); + } + } } public class Argb : BulkPixelOperationsTests { // 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 ArraySizesData => new TheoryData { 7, 16, 1111 }; } @@ -28,46 +78,65 @@ } } - public abstract class BulkPixelOperationsTests + public abstract class BulkPixelOperationsTests : MeasureFixture where TColor : struct, IPixel { + protected BulkPixelOperationsTests(ITestOutputHelper output) + : base(output) + { + } + public static TheoryData ArraySizesData => new TheoryData { 7, 16, 1111 }; - - [Theory] - [MemberData(nameof(ArraySizesData))] - public void PackFromVector4(int count) + + private static BulkPixelOperations Operations => BulkPixelOperations.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( + internal static void TestOperation( TSource[] source, TDest[] expected, - Action, BufferPointer, BufferPointer> action) + Action, BufferPointer> action) where TSource : struct where TDest : struct { using (var buffers = new TestBuffers(source, expected)) { - action(BulkPixelOperations.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(), diff --git a/tests/ImageSharp.Tests/Common/BufferPointerTests.cs b/tests/ImageSharp.Tests/Common/BufferPointerTests.cs index 4b5847d53..c82b63f11 100644 --- a/tests/ImageSharp.Tests/Common/BufferPointerTests.cs +++ b/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; + } + } + + /// + /// sizeof(AlignedFoo) == sizeof(long) + /// + 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 colorBuf = new PinnedBuffer(fooz)) + { + BufferPointer orig = colorBuf.Slice(1); + BufferPointer 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 ap = new BufferPointer(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[] 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 apSource = new BufferPointer(source, pSource); - BufferPointer apDest = new BufferPointer(dest, pDest); + BufferPointer apSource = new BufferPointer(source, pSource, 1); + BufferPointer apDest = new BufferPointer(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 apSource = new BufferPointer(source, pSource, 1); + BufferPointer apDest = new BufferPointer(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 apSource = new BufferPointer(source, pSource, 1); + BufferPointer apDest = new BufferPointer(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 apSource = new BufferPointer(source, pSource); + BufferPointer apSource = new BufferPointer(source, pSource, 1); + BufferPointer apDest = new BufferPointer(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 apSource = new BufferPointer(source, pSource, 1); + BufferPointer apDest = new BufferPointer(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 apSource = new BufferPointer(source, pSource); BufferPointer apDest = new BufferPointer(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 colorBuf = new PinnedBuffer(colors)) + using (PinnedBuffer byteBuf = new PinnedBuffer(colors.Length*4)) + { + BufferPointer.Copy(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); + } + } } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Common/PinnedBufferTests.cs b/tests/ImageSharp.Tests/Common/PinnedBufferTests.cs index 65077ae7f..3688763b9 100644 --- a/tests/ImageSharp.Tests/Common/PinnedBufferTests.cs +++ b/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 buffer = new PinnedBuffer(a)) + { + buffer.Clear(); + + Assert.Equal(default(Foo), a[0]); + Assert.Equal(default(Foo), a[1]); + } + } + [Fact] public void Dispose() { diff --git a/tests/ImageSharp.Tests/Common/PixelDataPoolTests.cs b/tests/ImageSharp.Tests/Common/PixelDataPoolTests.cs index 001785d60..403dffba9 100644 --- a/tests/ImageSharp.Tests/Common/PixelDataPoolTests.cs +++ b/tests/ImageSharp.Tests/Common/PixelDataPoolTests.cs @@ -3,6 +3,7 @@ // Licensed under the Apache License, Version 2.0. // +// 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.Rent(i); - - Assert.True(pixels.All(p => p == default(Color))); - - PixelDataPool.Return(pixels); - } - - for (int i = 16; i < 1024; i += 16) - { - Color[] pixels = PixelDataPool.Rent(i); - - Assert.True(pixels.All(p => p == default(Color))); - - PixelDataPool.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.Rent(256); - - for (int i = 0; i < pixels.Length; i++) - { - pixels[i] = Color.Azure; - } - - Assert.True(pixels.All(p => p == Color.Azure)); - - PixelDataPool.Return(pixels); - - Assert.True(pixels.All(p => p == default(Color))); - } - [Theory] [InlineData(false)] [InlineData(true)] diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegUtilityTestFixture.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegUtilityTestFixture.cs index 736225680..252b01138 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegUtilityTestFixture.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegUtilityTestFixture.cs @@ -4,14 +4,13 @@ // 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 - { - 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); diff --git a/tests/ImageSharp.Tests/Image/PixelAccessorTests.cs b/tests/ImageSharp.Tests/Image/PixelAccessorTests.cs index f3cd20f45..cd9cd04b7 100644 --- a/tests/ImageSharp.Tests/Image/PixelAccessorTests.cs +++ b/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(Image image) where TColor : struct, IPixel { diff --git a/tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs b/tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs new file mode 100644 index 000000000..333b645de --- /dev/null +++ b/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, IEqualityComparer + { + 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(); + } + } +} \ No newline at end of file diff --git a/theme/index.cshtml b/theme/index.cshtml new file mode 100644 index 000000000..d3656f800 --- /dev/null +++ b/theme/index.cshtml @@ -0,0 +1,3 @@ +Title: Home +--- +Welcome to the documentation for ImageSharp \ No newline at end of file