diff --git a/K4AdotNet.Tests.Record/K4AdotNet.Tests.Record.csproj b/K4AdotNet.Tests.Record/K4AdotNet.Tests.Record.csproj index da4bbcd..1162b2b 100644 --- a/K4AdotNet.Tests.Record/K4AdotNet.Tests.Record.csproj +++ b/K4AdotNet.Tests.Record/K4AdotNet.Tests.Record.csproj @@ -3,7 +3,7 @@ - netcoreapp2.1 + netcoreapp3.1 false AnyCPU Integration tests on Record API from K4AdotNet library diff --git a/K4AdotNet.Tests.Unit/K4AdotNet.Tests.Unit.csproj b/K4AdotNet.Tests.Unit/K4AdotNet.Tests.Unit.csproj index 29a1d28..a271537 100644 --- a/K4AdotNet.Tests.Unit/K4AdotNet.Tests.Unit.csproj +++ b/K4AdotNet.Tests.Unit/K4AdotNet.Tests.Unit.csproj @@ -3,7 +3,7 @@ - netcoreapp2.1 + netcoreapp3.1 false AnyCPU Unit tests on types from K4AdotNet library diff --git a/K4AdotNet.Tests.Unit/Sensor/ImageTests.cs b/K4AdotNet.Tests.Unit/Sensor/ImageTests.cs index 2119cae..122eb6e 100644 --- a/K4AdotNet.Tests.Unit/Sensor/ImageTests.cs +++ b/K4AdotNet.Tests.Unit/Sensor/ImageTests.cs @@ -127,6 +127,52 @@ public void TestCreationFromArray() } } + [TestMethod] + public void TestCreationFromMemory() + { + var format = ImageFormat.Depth16; + var strideBytes = format.StrideBytes(testWidth); + var lengthElements = testWidth * testHeight; + var array = new short[lengthElements]; + var owner = new TestMemoryOwner(array); + + using (var image = Image.CreateFromMemory(owner, format, testWidth, testHeight, strideBytes)) + { + Assert.AreNotEqual(IntPtr.Zero, image.Buffer); + Assert.AreEqual(format, image.Format); + Assert.AreEqual(testWidth, image.WidthPixels); + Assert.AreEqual(testHeight, image.HeightPixels); + Assert.AreEqual(strideBytes, image.StrideBytes); + Assert.AreEqual(owner.Memory.Length * sizeof(short), image.SizeBytes); + + // Check that Buffer points to array + for (var i = 0; i < array.Length; i++) + array[i] = unchecked((short)i); + + var buffer = image.Buffer; + for (var i = 0; i < array.Length; i++) + Assert.AreEqual(array[i], Marshal.ReadInt16(buffer, i * sizeof(short))); + + Marshal.WriteInt16(buffer, ofs: 123 * sizeof(short), val: 2019); + Assert.AreEqual(2019, array[123]); + + Assert.IsFalse(owner.IsDisposed); + } + + Assert.IsTrue(owner.IsDisposed); + } + + private sealed class TestMemoryOwner : System.Buffers.IMemoryOwner + { + private readonly short[] buffer; + private volatile bool isDisposed; + public TestMemoryOwner(short[] buffer) => this.buffer = buffer; + public Memory Memory => buffer; + public bool IsDisposed => isDisposed; + public void Dispose() => isDisposed = true; + + } + #endregion #region Testing of IsDisposed property, Dispose() method and Disposed event diff --git a/K4AdotNet/K4AdotNet.csproj b/K4AdotNet/K4AdotNet.csproj index 5f9b684..498965f 100644 --- a/K4AdotNet/K4AdotNet.csproj +++ b/K4AdotNet/K4AdotNet.csproj @@ -13,6 +13,10 @@ https://github.com/bibigone/k4a.net/raw/master/K4AdotNet-64.png + + true + + K4AdotNet.xml Off @@ -46,7 +50,6 @@ - package\%(FileName)%(Extension) diff --git a/K4AdotNet/K4AdotNet.xml b/K4AdotNet/K4AdotNet.xml index d3a7509..4f853ce 100644 --- a/K4AdotNet/K4AdotNet.xml +++ b/K4AdotNet/K4AdotNet.xml @@ -6083,6 +6083,47 @@ or array is too small for specified image parameters. + + Creates new image for specified underlying memory owner with specified format and size in pixels. + Type of elements in underlying memory buffer. Must be value type. + Memory owner of underlying buffer. Cannot be . Object will pin and keep reference to this array during all lifetime. + Format of image. Must be format with known stride: . + Width of image in pixels. Must be positive. + Height of image in pixels. Must be positive. + Created image. Not . + + This version of method can be used only for with known dependency between image width in pixels and stride in bytes + and cannot be used for other formats. For details see . + For other formats use . + + points to pinned memory of . + + + or is equal to or less than zero + or memory of is too small for specified image parameters. + + + Image stride in bytes cannot be automatically calculated from for specified . + + + + + Creates new image for specified underlying memory owner with specified format and size in pixels. + Type of elements in underlying memory buffer. Must be value type. + Memory owner of underlying buffer. Cannot be . Object will pin and keep reference to this array during all lifetime. + Format of image. + Width of image in pixels. Must be positive. + Height of image in pixels. Must be positive. + Image stride in bytes (the number of bytes per horizontal line of the image). Must be non-negative. Zero value can be used for and . /// Created image. Not . + + points to pinned memory of . + + + or is equal to or less than zero + or is less than zero or is too small for specified + or memory of is too small for specified image parameters. + + Call this method to free unmanaged resources associated with current instance. @@ -6117,6 +6158,11 @@ Use this buffer to access the raw image data. This property cannot be called for disposed objects. + + Access to the underlying memory buffer via span. + Unmanaged type that is going to use for memory access. + Span view to the underlying memory buffer. + Get the image buffer size in bytes. Use this function to know what the size of the image buffer is returned by . diff --git a/K4AdotNet/Sensor/Image.cs b/K4AdotNet/Sensor/Image.cs index 3d5643d..10af705 100644 --- a/K4AdotNet/Sensor/Image.cs +++ b/K4AdotNet/Sensor/Image.cs @@ -191,6 +191,83 @@ public static Image CreateFromArray(T[] buffer, ImageFormat format, int width return Create(handle)!; } +#if NETSTANDARD2_1 + + /// Creates new image for specified underlying memory owner with specified format and size in pixels. + /// Type of elements in underlying memory buffer. Must be value type. + /// Memory owner of underlying buffer. Cannot be . Object will pin and keep reference to this array during all lifetime. + /// Format of image. Must be format with known stride: . + /// Width of image in pixels. Must be positive. + /// Height of image in pixels. Must be positive. + /// Created image. Not . + /// + /// This version of method can be used only for with known dependency between image width in pixels and stride in bytes + /// and cannot be used for other formats. For details see . + /// For other formats use . + /// + /// points to pinned memory of . + /// + /// + /// or is equal to or less than zero + /// or memory of is too small for specified image parameters. + /// + /// + /// Image stride in bytes cannot be automatically calculated from for specified . + /// + /// + public static Image CreateFromMemory(System.Buffers.IMemoryOwner memoryOwner, ImageFormat format, int widthPixels, int heightPixels) + where T : unmanaged + => CreateFromMemory(memoryOwner, format, widthPixels, heightPixels, format.StrideBytes(widthPixels)); + + + /// Creates new image for specified underlying memory owner with specified format and size in pixels. + /// Type of elements in underlying memory buffer. Must be value type. + /// Memory owner of underlying buffer. Cannot be . Object will pin and keep reference to this array during all lifetime. + /// Format of image. + /// Width of image in pixels. Must be positive. + /// Height of image in pixels. Must be positive. + /// Image stride in bytes (the number of bytes per horizontal line of the image). Must be non-negative. Zero value can be used for and . /// Created image. Not . + /// + /// points to pinned memory of . + /// + /// + /// or is equal to or less than zero + /// or is less than zero or is too small for specified + /// or memory of is too small for specified image parameters. + /// + public static unsafe Image CreateFromMemory(System.Buffers.IMemoryOwner memoryOwner, ImageFormat format, int widthPixels, int heightPixels, int strideBytes) + where T : unmanaged + { + if (memoryOwner is null) + throw new ArgumentNullException(nameof(memoryOwner)); + if (widthPixels <= 0) + throw new ArgumentOutOfRangeException(nameof(widthPixels)); + if (heightPixels <= 0) + throw new ArgumentOutOfRangeException(nameof(heightPixels)); + if (strideBytes < 0) + throw new ArgumentOutOfRangeException(nameof(strideBytes)); + if (format.HasKnownBytesPerPixel() && strideBytes < widthPixels * format.BytesPerPixel()) + throw new ArgumentOutOfRangeException(nameof(strideBytes)); + + var memory = memoryOwner.Memory; + var sizeBytes = memory.Length * Marshal.SizeOf(); + if (strideBytes > 0 && sizeBytes < format.ImageSizeBytes(strideBytes, heightPixels)) + throw new ArgumentOutOfRangeException(nameof(memoryOwner) + "." + nameof(memoryOwner.Memory) + nameof(memory.Length)); + + var memoryPin = memory.Pin(); + var memoryPtr = new IntPtr(memoryPin.Pointer); + + var res = NativeApi.ImageCreateFromBuffer(format, widthPixels, heightPixels, strideBytes, + memoryPtr, Helpers.Int32ToUIntPtr(sizeBytes), pinnedMemoryReleaseCallback, PinnedMemoryContext.Create(memoryOwner, memoryPin), + out var handle); + if (res != NativeCallResults.Result.Succeeded || handle == null || handle.IsInvalid) + throw new ArgumentException($"Cannot create image with format {format}, size {widthPixels}x{heightPixels} pixels, stride {strideBytes} bytes from memory of size {sizeBytes} bytes."); + + return Create(handle)!; + } + +#endif + private void Handle_Disposed(object sender, EventArgs e) { handle.Disposed -= Handle_Disposed; @@ -233,6 +310,16 @@ public Image DuplicateReference() /// This property cannot be called for disposed objects. public IntPtr Buffer => NativeApi.ImageGetBuffer(handle.ValueNotDisposed); +#if NETSTANDARD2_1 + + /// Access to the underlying memory buffer via span. + /// Unmanaged type that is going to use for memory access. + /// Span view to the underlying memory buffer. + public unsafe Span GetSpan() where T : unmanaged + => new Span(Buffer.ToPointer(), SizeBytes / Marshal.SizeOf()); + +#endif + /// Get the image buffer size in bytes. /// Use this function to know what the size of the image buffer is returned by . /// This property cannot be called for disposed objects. @@ -483,7 +570,7 @@ public void FillFrom(int[] src) internal static NativeHandles.ImageHandle ToHandle(Image? image) => image?.handle?.ValueNotDisposed ?? NativeHandles.ImageHandle.Zero; - #region Equatable +#region Equatable /// Two images are equal when they reference to one and the same unmanaged object. /// Another image to be compared with this one. Can be . @@ -524,9 +611,9 @@ public override int GetHashCode() public override string ToString() => handle.ToString(); - #endregion +#endregion - #region Memory management +#region Memory management // This field is required to keep callback delegate in memory private static readonly NativeApi.MemoryDestroyCallback unmanagedBufferReleaseCallback @@ -542,6 +629,41 @@ private static readonly NativeApi.MemoryDestroyCallback pinnedArrayReleaseCallba private static void ReleasePinnedArray(IntPtr buffer, IntPtr context) => ((GCHandle)context).Free(); +#if NETSTANDARD2_1 + + private struct PinnedMemoryContext + { + private IDisposable memoryOwner; + private System.Buffers.MemoryHandle memoryHandle; + + public static IntPtr Create(IDisposable memoryOwner, System.Buffers.MemoryHandle memoryHandle) + { + var context = new PinnedMemoryContext { memoryOwner = memoryOwner, memoryHandle = memoryHandle }; + var ptr = Marshal.AllocHGlobal(Marshal.SizeOf()); + Marshal.StructureToPtr(context, ptr, fDeleteOld: false); + return ptr; + } + + public static void Destroy(IntPtr ptr) + { + var context = Marshal.PtrToStructure(ptr); + context.memoryHandle.Dispose(); + context.memoryOwner.Dispose(); + Marshal.FreeHGlobal(ptr); + } + } + + // This field is required to keep callback delegate in memory + private static readonly NativeApi.MemoryDestroyCallback pinnedMemoryReleaseCallback + = new NativeApi.MemoryDestroyCallback(ReleasePinnedMemory); + + private static void ReleasePinnedMemory(IntPtr _, IntPtr context) + => PinnedMemoryContext.Destroy(context); + +#endif + #endregion + + } } diff --git a/Product.props b/Product.props index a408be1..b28b342 100644 --- a/Product.props +++ b/Product.props @@ -1,7 +1,7 @@ - 1.4.12 - 1.4.12.0 + 1.4.13 + 1.4.13.0 See https://github.com/bibigone/k4a.net/releases bibigone,baSSiLL diff --git a/ProductInfo.cs b/ProductInfo.cs index c57d438..fe5370b 100644 --- a/ProductInfo.cs +++ b/ProductInfo.cs @@ -7,5 +7,5 @@ [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] -[assembly: AssemblyVersion("1.4.12.0")] -[assembly: AssemblyFileVersion("1.4.12.0")] +[assembly: AssemblyVersion("1.4.13.0")] +[assembly: AssemblyFileVersion("1.4.13.0")] diff --git a/README.md b/README.md index 381a26d..b262e36 100644 --- a/README.md +++ b/README.md @@ -17,10 +17,9 @@ ## Key features * Written fully on C# -* No unsafe code in library **K4AdotNet** itself (only `DllImports` to SDKs) * CLS-compliant (can be used from any .Net-compatible language, including C#, F#, VB.Net) * Library **K4AdotNet** is compiled against **.NET Standard 2.0, 2.1** and **.NET Framework 4.6.1** target frameworks - * This makes it compatible with **.NET Core 2.0/3.1**, **.NET Framework 4.6.1** and later, **Unity 2018.1** and later, etc. + * This makes it compatible with **.NET 5**, **.NET Core 2.0-3.1**, **.NET Framework 4.6.1** and later, **Unity 2018.1** and later, etc. * See https://docs.microsoft.com/en-us/dotnet/standard/net-standard for details * Clean API, which is close to C/C++ native API from [Azure Kinect Sensor SDK](https://docs.microsoft.com/en-us/azure/Kinect-dk/sensor-sdk-download) and [Azure Kinect Body Tracking SDK](https://docs.microsoft.com/en-us/azure/Kinect-dk/body-sdk-download). * Plus useful helper methods, additional checks and meaningful exceptions. @@ -28,7 +27,7 @@ * Up-to-date with the latest versions of native SDKs * No additional dependencies * Except dependencies on native libraries (DLLs) from [Azure Kinect Sensor SDK](https://docs.microsoft.com/en-us/azure/Kinect-dk/sensor-sdk-download) and [Azure Kinect Body Tracking SDK](https://docs.microsoft.com/en-us/azure/Kinect-dk/body-sdk-download) - * Native libraries from [Azure Kinect Sensor SDK](https://docs.microsoft.com/en-us/azure/Kinect-dk/sensor-sdk-download) are included to repository(see `externals` directory) and [NuGet package](https://www.nuget.org/packages/K4AdotNet) + * Native libraries from [Azure Kinect Sensor SDK](https://docs.microsoft.com/en-us/azure/Kinect-dk/sensor-sdk-download) are included to repository (see `externals` directory) and [NuGet package](https://www.nuget.org/packages/K4AdotNet) * But native libraries from [Azure Kinect Body Tracking SDK](https://docs.microsoft.com/en-us/azure/Kinect-dk/body-sdk-download) are *not* included to repository. It is recommended to install [Azure Kinect Body Tracking SDK](https://docs.microsoft.com/en-us/azure/Kinect-dk/body-sdk-download) separately. For details see below * Plenty of powerful samples: * for .NET Core @@ -85,15 +84,6 @@ How to use Body Tracking runtime: See https://github.com/bibigone/k4a.net/releases -## Roadmap - -* More samples (Box-man, 3D view, IMU...) -* More unit and integration tests -* Find out how to convert MJPEG -> BGRA faster (implementation in `k4a.dll` is very slow) -* Test under Linux, samples for Linux (using [Avalonia UI Framework](http://avaloniaui.net/)?) -* Some hosting for HTML documentation ([DocFX](https://dotnet.github.io/docfx/) + [github.io](https://pages.github.com/)?) - - ## How to build * Open `K4AdotNet.sln` in Visual Studio 2019