Browse Source

Merge pull request #1419 from SixLabors/sp/image-wrap-ptr

New Image.WrapMemory<TPixel>(void*) overloads
js/color-alpha-handling
James Jackson-South 5 years ago
committed by GitHub
parent
commit
a603b96f70
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 233
      src/ImageSharp/Image.WrapMemory.cs
  2. 1
      src/ImageSharp/Memory/ByteMemoryManager{T}.cs
  3. 60
      src/ImageSharp/Memory/UnmanagedMemoryManager{T}.cs
  4. 81
      tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs

233
src/ImageSharp/Image.WrapMemory.cs

@ -16,8 +16,22 @@ namespace SixLabors.ImageSharp
public abstract partial class Image
{
/// <summary>
/// Wraps an existing contiguous memory area of 'width' x 'height' pixels,
/// allowing to view/manipulate it as an <see cref="Image{TPixel}"/> instance.
/// <para>
/// Wraps an existing contiguous memory area of 'width' x 'height' pixels allowing viewing/manipulation as
/// an <see cref="Image{TPixel}"/> instance.
/// </para>
/// <para>
/// Please note: using this method does not transfer the ownership of the underlying buffer of the input <see cref="Memory{T}"/>
/// to the new <see cref="Image{TPixel}"/> instance. This means that consumers of this method must ensure that the input buffer
/// is either self-contained, (for example, a <see cref="Memory{T}"/> instance wrapping a new array that was
/// created), or that the owning object is not disposed until the returned <see cref="Image{TPixel}"/> is disposed.
/// </para>
/// <para>
/// If the input <see cref="Memory{T}"/> instance is one retrieved from an <see cref="IMemoryOwner{T}"/> instance
/// rented from a memory pool (such as <see cref="MemoryPool{T}"/>), 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.
/// </para>
/// </summary>
/// <typeparam name="TPixel">The pixel type</typeparam>
/// <param name="configuration">The <see cref="Configuration"/></param>
@ -45,8 +59,22 @@ namespace SixLabors.ImageSharp
}
/// <summary>
/// Wraps an existing contiguous memory area of 'width' x 'height' pixels,
/// allowing to view/manipulate it as an <see cref="Image{TPixel}"/> instance.
/// <para>
/// Wraps an existing contiguous memory area of 'width' x 'height' pixels allowing viewing/manipulation as
/// an <see cref="Image{TPixel}"/> instance.
/// </para>
/// <para>
/// Please note: using this method does not transfer the ownership of the underlying buffer of the input <see cref="Memory{T}"/>
/// to the new <see cref="Image{TPixel}"/> instance. This means that consumers of this method must ensure that the input buffer
/// is either self-contained, (for example, a <see cref="Memory{T}"/> instance wrapping a new array that was
/// created), or that the owning object is not disposed until the returned <see cref="Image{TPixel}"/> is disposed.
/// </para>
/// <para>
/// If the input <see cref="Memory{T}"/> instance is one retrieved from an <see cref="IMemoryOwner{T}"/> instance
/// rented from a memory pool (such as <see cref="MemoryPool{T}"/>), 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.
/// </para>
/// </summary>
/// <typeparam name="TPixel">The pixel type</typeparam>
/// <param name="configuration">The <see cref="Configuration"/></param>
@ -64,9 +92,22 @@ namespace SixLabors.ImageSharp
=> WrapMemory(configuration, pixelMemory, width, height, new ImageMetadata());
/// <summary>
/// Wraps an existing contiguous memory area of 'width' x 'height' pixels,
/// allowing to view/manipulate it as an <see cref="Image{TPixel}"/> instance.
/// The memory is being observed, the caller remains responsible for managing it's lifecycle.
/// <para>
/// Wraps an existing contiguous memory area of 'width' x 'height' pixels allowing viewing/manipulation as
/// an <see cref="Image{TPixel}"/> instance.
/// </para>
/// <para>
/// Please note: using this method does not transfer the ownership of the underlying buffer of the input <see cref="Memory{T}"/>
/// to the new <see cref="Image{TPixel}"/> instance. This means that consumers of this method must ensure that the input buffer
/// is either self-contained, (for example, a <see cref="Memory{T}"/> instance wrapping a new array that was
/// created), or that the owning object is not disposed until the returned <see cref="Image{TPixel}"/> is disposed.
/// </para>
/// <para>
/// If the input <see cref="Memory{T}"/> instance is one retrieved from an <see cref="IMemoryOwner{T}"/> instance
/// rented from a memory pool (such as <see cref="MemoryPool{T}"/>), 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.
/// </para>
/// </summary>
/// <typeparam name="TPixel">The pixel type.</typeparam>
/// <param name="pixelMemory">The pixel memory.</param>
@ -154,8 +195,22 @@ namespace SixLabors.ImageSharp
=> WrapMemory(Configuration.Default, pixelMemoryOwner, width, height);
/// <summary>
/// Wraps an existing contiguous memory area of 'width' x 'height' pixels,
/// allowing to view/manipulate it as an <see cref="Image{TPixel}"/> instance.
/// <para>
/// Wraps an existing contiguous memory area of 'width' x 'height' pixels allowing viewing/manipulation as
/// an <see cref="Image{TPixel}"/> instance.
/// </para>
/// <para>
/// Please note: using this method does not transfer the ownership of the underlying buffer of the input <see cref="Memory{T}"/>
/// to the new <see cref="Image{TPixel}"/> instance. This means that consumers of this method must ensure that the input buffer
/// is either self-contained, (for example, a <see cref="Memory{T}"/> instance wrapping a new array that was
/// created), or that the owning object is not disposed until the returned <see cref="Image{TPixel}"/> is disposed.
/// </para>
/// <para>
/// If the input <see cref="Memory{T}"/> instance is one retrieved from an <see cref="IMemoryOwner{T}"/> instance
/// rented from a memory pool (such as <see cref="MemoryPool{T}"/>), 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.
/// </para>
/// </summary>
/// <typeparam name="TPixel">The pixel type</typeparam>
/// <param name="configuration">The <see cref="Configuration"/></param>
@ -186,8 +241,22 @@ namespace SixLabors.ImageSharp
}
/// <summary>
/// Wraps an existing contiguous memory area of 'width' x 'height' pixels,
/// allowing to view/manipulate it as an <see cref="Image{TPixel}"/> instance.
/// <para>
/// Wraps an existing contiguous memory area of 'width' x 'height' pixels allowing viewing/manipulation as
/// an <see cref="Image{TPixel}"/> instance.
/// </para>
/// <para>
/// Please note: using this method does not transfer the ownership of the underlying buffer of the input <see cref="Memory{T}"/>
/// to the new <see cref="Image{TPixel}"/> instance. This means that consumers of this method must ensure that the input buffer
/// is either self-contained, (for example, a <see cref="Memory{T}"/> instance wrapping a new array that was
/// created), or that the owning object is not disposed until the returned <see cref="Image{TPixel}"/> is disposed.
/// </para>
/// <para>
/// If the input <see cref="Memory{T}"/> instance is one retrieved from an <see cref="IMemoryOwner{T}"/> instance
/// rented from a memory pool (such as <see cref="MemoryPool{T}"/>), 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.
/// </para>
/// </summary>
/// <typeparam name="TPixel">The pixel type</typeparam>
/// <param name="configuration">The <see cref="Configuration"/></param>
@ -205,9 +274,22 @@ namespace SixLabors.ImageSharp
=> WrapMemory<TPixel>(configuration, byteMemory, width, height, new ImageMetadata());
/// <summary>
/// Wraps an existing contiguous memory area of 'width' x 'height' pixels,
/// allowing to view/manipulate it as an <see cref="Image{TPixel}"/> instance.
/// The memory is being observed, the caller remains responsible for managing it's lifecycle.
/// <para>
/// Wraps an existing contiguous memory area of 'width' x 'height' pixels allowing viewing/manipulation as
/// an <see cref="Image{TPixel}"/> instance.
/// </para>
/// <para>
/// Please note: using this method does not transfer the ownership of the underlying buffer of the input <see cref="Memory{T}"/>
/// to the new <see cref="Image{TPixel}"/> instance. This means that consumers of this method must ensure that the input buffer
/// is either self-contained, (for example, a <see cref="Memory{T}"/> instance wrapping a new array that was
/// created), or that the owning object is not disposed until the returned <see cref="Image{TPixel}"/> is disposed.
/// </para>
/// <para>
/// If the input <see cref="Memory{T}"/> instance is one retrieved from an <see cref="IMemoryOwner{T}"/> instance
/// rented from a memory pool (such as <see cref="MemoryPool{T}"/>), 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.
/// </para>
/// </summary>
/// <typeparam name="TPixel">The pixel type.</typeparam>
/// <param name="byteMemory">The byte memory representing the pixel data.</param>
@ -220,5 +302,128 @@ namespace SixLabors.ImageSharp
int height)
where TPixel : unmanaged, IPixel<TPixel>
=> WrapMemory<TPixel>(Configuration.Default, byteMemory, width, height);
/// <summary>
/// <para>
/// Wraps an existing contiguous memory area of 'width' x 'height' pixels allowing viewing/manipulation as
/// an <see cref="Image{TPixel}"/> instance.
/// </para>
/// <para>
/// 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
/// <see cref="Image{TPixel}"/> 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 <see cref="Image{TPixel}"/> 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 <see cref="Image{TPixel}"/> instance is in use.
/// Failing to do so constitutes undefined behavior and will likely lead to memory corruption and runtime crashes.
/// </para>
/// <para>
/// Note also that if you have a <see cref="Memory{T}"/> or an array (which can be cast to <see cref="Memory{T}"/>) of
/// either <see cref="byte"/> or <typeparamref name="TPixel"/> values, it is highly recommended to use one of the other
/// available overloads of this method instead (such as <see cref="WrapMemory{TPixel}(Configuration, Memory{byte}, int, int)"/>
/// or <see cref="WrapMemory{TPixel}(Configuration, Memory{TPixel}, int, int)"/>, 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.
/// </para>
/// </summary>
/// <typeparam name="TPixel">The pixel type</typeparam>
/// <param name="configuration">The <see cref="Configuration"/></param>
/// <param name="pointer">The pointer to the target memory buffer to wrap.</param>
/// <param name="width">The width of the memory image.</param>
/// <param name="height">The height of the memory image.</param>
/// <param name="metadata">The <see cref="ImageMetadata"/>.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The metadata is null.</exception>
/// <returns>An <see cref="Image{TPixel}"/> instance</returns>
public static unsafe Image<TPixel> WrapMemory<TPixel>(
Configuration configuration,
void* pointer,
int width,
int height,
ImageMetadata metadata)
where TPixel : unmanaged, IPixel<TPixel>
{
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<TPixel>(pointer, width * height);
var memorySource = MemoryGroup<TPixel>.Wrap(memoryManager.Memory);
return new Image<TPixel>(configuration, memorySource, width, height, metadata);
}
/// <summary>
/// <para>
/// Wraps an existing contiguous memory area of 'width' x 'height' pixels allowing viewing/manipulation as
/// an <see cref="Image{TPixel}"/> instance.
/// </para>
/// <para>
/// 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
/// <see cref="Image{TPixel}"/> 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 <see cref="Image{TPixel}"/> 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 <see cref="Image{TPixel}"/> instance is in use.
/// Failing to do so constitutes undefined behavior and will likely lead to memory corruption and runtime crashes.
/// </para>
/// <para>
/// Note also that if you have a <see cref="Memory{T}"/> or an array (which can be cast to <see cref="Memory{T}"/>) of
/// either <see cref="byte"/> or <typeparamref name="TPixel"/> values, it is highly recommended to use one of the other
/// available overloads of this method instead (such as <see cref="WrapMemory{TPixel}(Configuration, Memory{byte}, int, int)"/>
/// or <see cref="WrapMemory{TPixel}(Configuration, Memory{TPixel}, int, int)"/>, 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.
/// </para>
/// </summary>
/// <typeparam name="TPixel">The pixel type</typeparam>
/// <param name="configuration">The <see cref="Configuration"/></param>
/// <param name="pointer">The pointer to the target memory buffer to wrap.</param>
/// <param name="width">The width of the memory image.</param>
/// <param name="height">The height of the memory image.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <returns>An <see cref="Image{TPixel}"/> instance.</returns>
public static unsafe Image<TPixel> WrapMemory<TPixel>(
Configuration configuration,
void* pointer,
int width,
int height)
where TPixel : unmanaged, IPixel<TPixel>
=> WrapMemory<TPixel>(configuration, pointer, width, height, new ImageMetadata());
/// <summary>
/// <para>
/// Wraps an existing contiguous memory area of 'width' x 'height' pixels allowing viewing/manipulation as
/// an <see cref="Image{TPixel}"/> instance.
/// </para>
/// <para>
/// 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
/// <see cref="Image{TPixel}"/> 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 <see cref="Image{TPixel}"/> 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 <see cref="Image{TPixel}"/> instance is in use.
/// Failing to do so constitutes undefined behavior and will likely lead to memory corruption and runtime crashes.
/// </para>
/// <para>
/// Note also that if you have a <see cref="Memory{T}"/> or an array (which can be cast to <see cref="Memory{T}"/>) of
/// either <see cref="byte"/> or <typeparamref name="TPixel"/> values, it is highly recommended to use one of the other
/// available overloads of this method instead (such as <see cref="WrapMemory{TPixel}(Configuration, Memory{byte}, int, int)"/>
/// or <see cref="WrapMemory{TPixel}(Configuration, Memory{TPixel}, int, int)"/>, 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.
/// </para>
/// </summary>
/// <typeparam name="TPixel">The pixel type.</typeparam>
/// <param name="pointer">The pointer to the target memory buffer to wrap.</param>
/// <param name="width">The width of the memory image.</param>
/// <param name="height">The height of the memory image.</param>
/// <returns>An <see cref="Image{TPixel}"/> instance.</returns>
public static unsafe Image<TPixel> WrapMemory<TPixel>(
void* pointer,
int width,
int height)
where TPixel : unmanaged, IPixel<TPixel>
=> WrapMemory<TPixel>(Configuration.Default, pointer, width, height);
}
}

1
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;

60
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
{
/// <summary>
/// A custom <see cref="MemoryManager{T}"/> that can wrap a rawpointer to a buffer of a specified type.
/// </summary>
/// <typeparam name="T">The value type to use when casting the wrapped <see cref="Memory{T}"/> instance.</typeparam>
/// <remarks>This manager doesn't own the memory buffer that it points to.</remarks>
internal sealed unsafe class UnmanagedMemoryManager<T> : MemoryManager<T>
where T : unmanaged
{
/// <summary>
/// The pointer to the memory buffer.
/// </summary>
private readonly void* pointer;
/// <summary>
/// The length of the memory area.
/// </summary>
private readonly int length;
/// <summary>
/// Initializes a new instance of the <see cref="UnmanagedMemoryManager{T}"/> class.
/// </summary>
/// <param name="pointer">The pointer to the memory buffer.</param>
/// <param name="length">The length of the memory area.</param>
public UnmanagedMemoryManager(void* pointer, int length)
{
this.pointer = pointer;
this.length = length;
}
/// <inheritdoc/>
protected override void Dispose(bool disposing)
{
}
/// <inheritdoc/>
public override Span<T> GetSpan()
{
return new Span<T>(this.pointer, this.length);
}
/// <inheritdoc/>
public override MemoryHandle Pin(int elementIndex = 0)
{
return new MemoryHandle(((T*)this.pointer) + elementIndex);
}
/// <inheritdoc/>
public override void Unpin()
{
}
}
}

81
tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs

@ -282,6 +282,76 @@ namespace SixLabors.ImageSharp.Tests
}
}
[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<Rgba32>(cfg, ptr, 5, 5, metaData))
{
Assert.True(image.TryGetSinglePixelSpan(out Span<Rgba32> 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<Bgra32> pixelMemory = memoryManager.Memory;
Bgra32 bg = Color.Red;
Bgra32 fg = Color.Green;
fixed (void* p = pixelMemory.Span)
{
using (var image = Image.WrapMemory<Bgra32>(p, bmp.Width, bmp.Height))
{
Span<Bgra32> pixelSpan = pixelMemory.Span;
Span<Bgra32> 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 @@ namespace SixLabors.ImageSharp.Tests
Assert.Throws<ArgumentException>(() => Image.WrapMemory<Rgba32>(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<ArgumentException>(() => Image.WrapMemory<Rgba32>((void*)null, height, width));
}
private static bool ShouldSkipBitmapTest =>
!TestEnvironment.Is64BitProcess || (TestHelpers.ImageSharpBuiltAgainst != "netcoreapp3.1" && TestHelpers.ImageSharpBuiltAgainst != "netcoreapp2.1");
}

Loading…
Cancel
Save