diff --git a/src/ImageSharp/Image.WrapMemory.cs b/src/ImageSharp/Image.WrapMemory.cs
index d89c44dc56..383f643969 100644
--- a/src/ImageSharp/Image.WrapMemory.cs
+++ b/src/ImageSharp/Image.WrapMemory.cs
@@ -16,8 +16,22 @@ namespace SixLabors.ImageSharp
public abstract partial class Image
{
///
- /// Wraps an existing contiguous memory area of 'width' x 'height' pixels,
- /// allowing to view/manipulate it as an instance.
+ ///
+ /// Wraps an existing contiguous memory area of 'width' x 'height' pixels allowing viewing/manipulation as
+ /// an instance.
+ ///
+ ///
+ /// Please note: using this method does not transfer the ownership of the underlying buffer of the input
+ /// to the new instance. This means that consumers of this method must ensure that the input buffer
+ /// is either self-contained, (for example, a instance wrapping a new array that was
+ /// created), or that the owning object is not disposed until the returned is disposed.
+ ///
+ ///
+ /// If the input instance is one retrieved from an instance
+ /// rented from a memory pool (such as ), and that owning instance is disposed while the image is still
+ /// in use, this will lead to undefined behavior and possibly runtime crashes (as the same buffer might then be modified by other
+ /// consumers while the returned image is still working on it). Make sure to control the lifetime of the input buffers appropriately.
+ ///
///
/// The pixel type
/// The
@@ -45,8 +59,22 @@ public static Image WrapMemory(
}
///
- /// Wraps an existing contiguous memory area of 'width' x 'height' pixels,
- /// allowing to view/manipulate it as an instance.
+ ///
+ /// Wraps an existing contiguous memory area of 'width' x 'height' pixels allowing viewing/manipulation as
+ /// an instance.
+ ///
+ ///
+ /// Please note: using this method does not transfer the ownership of the underlying buffer of the input
+ /// to the new instance. This means that consumers of this method must ensure that the input buffer
+ /// is either self-contained, (for example, a instance wrapping a new array that was
+ /// created), or that the owning object is not disposed until the returned is disposed.
+ ///
+ ///
+ /// If the input instance is one retrieved from an instance
+ /// rented from a memory pool (such as ), and that owning instance is disposed while the image is still
+ /// in use, this will lead to undefined behavior and possibly runtime crashes (as the same buffer might then be modified by other
+ /// consumers while the returned image is still working on it). Make sure to control the lifetime of the input buffers appropriately.
+ ///
///
/// The pixel type
/// The
@@ -64,9 +92,22 @@ public static Image WrapMemory(
=> WrapMemory(configuration, pixelMemory, width, height, new ImageMetadata());
///
- /// Wraps an existing contiguous memory area of 'width' x 'height' pixels,
- /// allowing to view/manipulate it as an instance.
- /// The memory is being observed, the caller remains responsible for managing it's lifecycle.
+ ///
+ /// Wraps an existing contiguous memory area of 'width' x 'height' pixels allowing viewing/manipulation as
+ /// an instance.
+ ///
+ ///
+ /// Please note: using this method does not transfer the ownership of the underlying buffer of the input
+ /// to the new instance. This means that consumers of this method must ensure that the input buffer
+ /// is either self-contained, (for example, a instance wrapping a new array that was
+ /// created), or that the owning object is not disposed until the returned is disposed.
+ ///
+ ///
+ /// If the input instance is one retrieved from an instance
+ /// rented from a memory pool (such as ), and that owning instance is disposed while the image is still
+ /// in use, this will lead to undefined behavior and possibly runtime crashes (as the same buffer might then be modified by other
+ /// consumers while the returned image is still working on it). Make sure to control the lifetime of the input buffers appropriately.
+ ///
///
/// The pixel type.
/// The pixel memory.
@@ -154,8 +195,22 @@ public static Image WrapMemory(
=> WrapMemory(Configuration.Default, pixelMemoryOwner, width, height);
///
- /// Wraps an existing contiguous memory area of 'width' x 'height' pixels,
- /// allowing to view/manipulate it as an instance.
+ ///
+ /// Wraps an existing contiguous memory area of 'width' x 'height' pixels allowing viewing/manipulation as
+ /// an instance.
+ ///
+ ///
+ /// Please note: using this method does not transfer the ownership of the underlying buffer of the input
+ /// to the new instance. This means that consumers of this method must ensure that the input buffer
+ /// is either self-contained, (for example, a instance wrapping a new array that was
+ /// created), or that the owning object is not disposed until the returned is disposed.
+ ///
+ ///
+ /// If the input instance is one retrieved from an instance
+ /// rented from a memory pool (such as ), and that owning instance is disposed while the image is still
+ /// in use, this will lead to undefined behavior and possibly runtime crashes (as the same buffer might then be modified by other
+ /// consumers while the returned image is still working on it). Make sure to control the lifetime of the input buffers appropriately.
+ ///
///
/// The pixel type
/// The
@@ -186,8 +241,22 @@ public static Image WrapMemory(
}
///
- /// Wraps an existing contiguous memory area of 'width' x 'height' pixels,
- /// allowing to view/manipulate it as an instance.
+ ///
+ /// Wraps an existing contiguous memory area of 'width' x 'height' pixels allowing viewing/manipulation as
+ /// an instance.
+ ///
+ ///
+ /// Please note: using this method does not transfer the ownership of the underlying buffer of the input
+ /// to the new instance. This means that consumers of this method must ensure that the input buffer
+ /// is either self-contained, (for example, a instance wrapping a new array that was
+ /// created), or that the owning object is not disposed until the returned is disposed.
+ ///
+ ///
+ /// If the input instance is one retrieved from an instance
+ /// rented from a memory pool (such as ), and that owning instance is disposed while the image is still
+ /// in use, this will lead to undefined behavior and possibly runtime crashes (as the same buffer might then be modified by other
+ /// consumers while the returned image is still working on it). Make sure to control the lifetime of the input buffers appropriately.
+ ///
///
/// The pixel type
/// The
@@ -205,9 +274,22 @@ public static Image WrapMemory(
=> WrapMemory(configuration, byteMemory, width, height, new ImageMetadata());
///
- /// Wraps an existing contiguous memory area of 'width' x 'height' pixels,
- /// allowing to view/manipulate it as an instance.
- /// The memory is being observed, the caller remains responsible for managing it's lifecycle.
+ ///
+ /// Wraps an existing contiguous memory area of 'width' x 'height' pixels allowing viewing/manipulation as
+ /// an instance.
+ ///
+ ///
+ /// Please note: using this method does not transfer the ownership of the underlying buffer of the input
+ /// to the new instance. This means that consumers of this method must ensure that the input buffer
+ /// is either self-contained, (for example, a instance wrapping a new array that was
+ /// created), or that the owning object is not disposed until the returned is disposed.
+ ///
+ ///
+ /// If the input instance is one retrieved from an instance
+ /// rented from a memory pool (such as ), and that owning instance is disposed while the image is still
+ /// in use, this will lead to undefined behavior and possibly runtime crashes (as the same buffer might then be modified by other
+ /// consumers while the returned image is still working on it). Make sure to control the lifetime of the input buffers appropriately.
+ ///
///
/// The pixel type.
/// The byte memory representing the pixel data.
@@ -220,5 +302,128 @@ public static Image WrapMemory(
int height)
where TPixel : unmanaged, IPixel
=> WrapMemory(Configuration.Default, byteMemory, width, height);
+
+ ///
+ ///
+ /// Wraps an existing contiguous memory area of 'width' x 'height' pixels allowing viewing/manipulation as
+ /// an instance.
+ ///
+ ///
+ /// Please note: this method relies on callers to carefully manage the target memory area being referenced by the
+ /// pointer and that the lifetime of such a memory area is at least equal to that of the returned
+ /// instance. For example, if the input pointer references an unmanaged memory area,
+ /// callers must ensure that the memory area is not freed as long as the returned is
+ /// in use and not disposed. The same applies if the input memory area points to a pinned managed object, as callers
+ /// must ensure that objects will remain pinned as long as the instance is in use.
+ /// Failing to do so constitutes undefined behavior and will likely lead to memory corruption and runtime crashes.
+ ///
+ ///
+ /// Note also that if you have a or an array (which can be cast to ) of
+ /// either or values, it is highly recommended to use one of the other
+ /// available overloads of this method instead (such as
+ /// or , to make the resulting code less error
+ /// prone and avoid having to pin the underlying memory buffer in use. This method is primarily meant to be used when
+ /// doing interop or working with buffers that are located in unmanaged memory.
+ ///
+ ///
+ /// The pixel type
+ /// The
+ /// The pointer to the target memory buffer to wrap.
+ /// The width of the memory image.
+ /// The height of the memory image.
+ /// The .
+ /// The configuration is null.
+ /// The metadata is null.
+ /// An instance
+ public static unsafe Image WrapMemory(
+ Configuration configuration,
+ void* pointer,
+ int width,
+ int height,
+ ImageMetadata metadata)
+ where TPixel : unmanaged, IPixel
+ {
+ Guard.IsFalse(pointer == null, nameof(pointer), "Pointer must be not null");
+ Guard.NotNull(configuration, nameof(configuration));
+ Guard.NotNull(metadata, nameof(metadata));
+
+ var memoryManager = new UnmanagedMemoryManager(pointer, width * height);
+
+ var memorySource = MemoryGroup.Wrap(memoryManager.Memory);
+ return new Image(configuration, memorySource, width, height, metadata);
+ }
+
+ ///
+ ///
+ /// Wraps an existing contiguous memory area of 'width' x 'height' pixels allowing viewing/manipulation as
+ /// an instance.
+ ///
+ ///
+ /// Please note: this method relies on callers to carefully manage the target memory area being referenced by the
+ /// pointer and that the lifetime of such a memory area is at least equal to that of the returned
+ /// instance. For example, if the input pointer references an unmanaged memory area,
+ /// callers must ensure that the memory area is not freed as long as the returned is
+ /// in use and not disposed. The same applies if the input memory area points to a pinned managed object, as callers
+ /// must ensure that objects will remain pinned as long as the instance is in use.
+ /// Failing to do so constitutes undefined behavior and will likely lead to memory corruption and runtime crashes.
+ ///
+ ///
+ /// Note also that if you have a or an array (which can be cast to ) of
+ /// either or values, it is highly recommended to use one of the other
+ /// available overloads of this method instead (such as
+ /// or , to make the resulting code less error
+ /// prone and avoid having to pin the underlying memory buffer in use. This method is primarily meant to be used when
+ /// doing interop or working with buffers that are located in unmanaged memory.
+ ///
+ ///
+ /// The pixel type
+ /// The
+ /// The pointer to the target memory buffer to wrap.
+ /// The width of the memory image.
+ /// The height of the memory image.
+ /// The configuration is null.
+ /// An instance.
+ public static unsafe Image WrapMemory(
+ Configuration configuration,
+ void* pointer,
+ int width,
+ int height)
+ where TPixel : unmanaged, IPixel
+ => WrapMemory(configuration, pointer, width, height, new ImageMetadata());
+
+ ///
+ ///
+ /// Wraps an existing contiguous memory area of 'width' x 'height' pixels allowing viewing/manipulation as
+ /// an instance.
+ ///
+ ///
+ /// Please note: this method relies on callers to carefully manage the target memory area being referenced by the
+ /// pointer and that the lifetime of such a memory area is at least equal to that of the returned
+ /// instance. For example, if the input pointer references an unmanaged memory area,
+ /// callers must ensure that the memory area is not freed as long as the returned is
+ /// in use and not disposed. The same applies if the input memory area points to a pinned managed object, as callers
+ /// must ensure that objects will remain pinned as long as the instance is in use.
+ /// Failing to do so constitutes undefined behavior and will likely lead to memory corruption and runtime crashes.
+ ///
+ ///
+ /// Note also that if you have a or an array (which can be cast to ) of
+ /// either or values, it is highly recommended to use one of the other
+ /// available overloads of this method instead (such as
+ /// or , to make the resulting code less error
+ /// prone and avoid having to pin the underlying memory buffer in use. This method is primarily meant to be used when
+ /// doing interop or working with buffers that are located in unmanaged memory.
+ ///
+ ///
+ /// The pixel type.
+ /// The pointer to the target memory buffer to wrap.
+ /// The width of the memory image.
+ /// The height of the memory image.
+ /// An instance.
+ public static unsafe Image WrapMemory(
+ void* pointer,
+ int width,
+ int height)
+ where TPixel : unmanaged, IPixel
+ => WrapMemory(Configuration.Default, pointer, width, height);
}
}
diff --git a/src/ImageSharp/Memory/ByteMemoryManager{T}.cs b/src/ImageSharp/Memory/ByteMemoryManager{T}.cs
index 223709df65..1731639582 100644
--- a/src/ImageSharp/Memory/ByteMemoryManager{T}.cs
+++ b/src/ImageSharp/Memory/ByteMemoryManager{T}.cs
@@ -1,5 +1,6 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
+
using System;
using System.Buffers;
using System.Runtime.CompilerServices;
diff --git a/src/ImageSharp/Memory/UnmanagedMemoryManager{T}.cs b/src/ImageSharp/Memory/UnmanagedMemoryManager{T}.cs
new file mode 100644
index 0000000000..58eaee320a
--- /dev/null
+++ b/src/ImageSharp/Memory/UnmanagedMemoryManager{T}.cs
@@ -0,0 +1,60 @@
+// Copyright (c) Six Labors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Buffers;
+
+namespace SixLabors.ImageSharp.Memory
+{
+ ///
+ /// A custom that can wrap a rawpointer to a buffer of a specified type.
+ ///
+ /// The value type to use when casting the wrapped instance.
+ /// This manager doesn't own the memory buffer that it points to.
+ internal sealed unsafe class UnmanagedMemoryManager : MemoryManager
+ where T : unmanaged
+ {
+ ///
+ /// The pointer to the memory buffer.
+ ///
+ private readonly void* pointer;
+
+ ///
+ /// The length of the memory area.
+ ///
+ private readonly int length;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The pointer to the memory buffer.
+ /// The length of the memory area.
+ public UnmanagedMemoryManager(void* pointer, int length)
+ {
+ this.pointer = pointer;
+ this.length = length;
+ }
+
+ ///
+ protected override void Dispose(bool disposing)
+ {
+ }
+
+ ///
+ public override Span GetSpan()
+ {
+ return new Span(this.pointer, this.length);
+ }
+
+ ///
+ public override MemoryHandle Pin(int elementIndex = 0)
+ {
+ return new MemoryHandle(((T*)this.pointer) + elementIndex);
+ }
+
+ ///
+ public override void Unpin()
+ {
+ }
+ }
+}
diff --git a/tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs b/tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs
index 7dc7dbb30c..02a8381808 100644
--- a/tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs
+++ b/tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs
@@ -282,6 +282,76 @@ public void WrapSystemDrawingBitmap_FromBytes_WhenObserved()
}
}
+ [Fact]
+ public unsafe void WrapMemory_FromPointer_CreatedImageIsCorrect()
+ {
+ var cfg = Configuration.CreateDefaultInstance();
+ var metaData = new ImageMetadata();
+
+ var array = new Rgba32[25];
+
+ fixed (void* ptr = array)
+ {
+ using (var image = Image.WrapMemory(cfg, ptr, 5, 5, metaData))
+ {
+ Assert.True(image.TryGetSinglePixelSpan(out Span imageSpan));
+ ref Rgba32 pixel0 = ref imageSpan[0];
+ Assert.True(Unsafe.AreSame(ref array[0], ref pixel0));
+ ref Rgba32 pixel_1 = ref imageSpan[imageSpan.Length - 1];
+ Assert.True(Unsafe.AreSame(ref array[array.Length - 1], ref pixel_1));
+
+ Assert.Equal(cfg, image.GetConfiguration());
+ Assert.Equal(metaData, image.Metadata);
+ }
+ }
+ }
+
+ [Fact]
+ public unsafe void WrapSystemDrawingBitmap_FromPointer()
+ {
+ if (ShouldSkipBitmapTest)
+ {
+ return;
+ }
+
+ using (var bmp = new Bitmap(51, 23))
+ {
+ using (var memoryManager = new BitmapMemoryManager(bmp))
+ {
+ Memory pixelMemory = memoryManager.Memory;
+ Bgra32 bg = Color.Red;
+ Bgra32 fg = Color.Green;
+
+ fixed (void* p = pixelMemory.Span)
+ {
+ using (var image = Image.WrapMemory(p, bmp.Width, bmp.Height))
+ {
+ Span pixelSpan = pixelMemory.Span;
+ Span imageSpan = image.GetRootFramePixelBuffer().GetSingleMemory().Span;
+
+ Assert.Equal(pixelSpan.Length, imageSpan.Length);
+ Assert.True(Unsafe.AreSame(ref pixelSpan.GetPinnableReference(), ref imageSpan.GetPinnableReference()));
+
+ Assert.True(image.TryGetSinglePixelSpan(out imageSpan));
+ imageSpan.Fill(bg);
+ for (var i = 10; i < 20; i++)
+ {
+ image.GetPixelRowSpan(i).Slice(10, 10).Fill(fg);
+ }
+ }
+
+ Assert.False(memoryManager.IsDisposed);
+ }
+ }
+
+ string fn = System.IO.Path.Combine(
+ TestEnvironment.ActualOutputDirectoryFullPath,
+ $"{nameof(this.WrapSystemDrawingBitmap_WhenObserved)}.bmp");
+
+ bmp.Save(fn, ImageFormat.Bmp);
+ }
+ }
+
[Theory]
[InlineData(0, 5, 5)]
[InlineData(20, 5, 5)]
@@ -333,6 +403,17 @@ public void WrapMemory_MemoryOfByte_InvalidSize(int size, int height, int width)
Assert.Throws(() => Image.WrapMemory(memory, height, width));
}
+ [Theory]
+ [InlineData(0, 5, 5)]
+ [InlineData(20, 5, 5)]
+ [InlineData(26, 5, 5)]
+ [InlineData(2, 1, 1)]
+ [InlineData(1023, 32, 32)]
+ public unsafe void WrapMemory_Pointer_Null(int size, int height, int width)
+ {
+ Assert.Throws(() => Image.WrapMemory((void*)null, height, width));
+ }
+
private static bool ShouldSkipBitmapTest =>
!TestEnvironment.Is64BitProcess || (TestHelpers.ImageSharpBuiltAgainst != "netcoreapp3.1" && TestHelpers.ImageSharpBuiltAgainst != "netcoreapp2.1");
}