Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions src/ImageSharp/Image.WrapMemory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -150,5 +150,70 @@ public static Image<TPixel> WrapMemory<TPixel>(
int height)
where TPixel : unmanaged, IPixel<TPixel>
=> 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 ImageSharp <see cref="Image{TPixel}"/> instance.
/// </summary>
/// <typeparam name="TPixel">The pixel type</typeparam>
/// <param name="configuration">The <see cref="Configuration"/></param>
/// <param name="byteMemory">The byte memory representing the pixel data.</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 Image<TPixel> WrapMemory<TPixel>(

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://docs.microsoft.com/en-us/dotnet/standard/memory-and-spans/memory-t-usage-guidelines

Seems beneficial to consider exposing IMemoryOwner<T> overloads of this to allow ownership transfer?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't include them here on purpose as they weren't part of the initial API proposal in #1097.
@antonfirsov should this be added here or would that be for another PR?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No preference, other than it's always easier to review multiple smaller (subsequent) PR-s.

Copy link
Member Author

@Sergio0694 Sergio0694 Aug 13, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds like a preference to me 😄
Will keep this PR restricted to consumed buffers then, and we can create another one after this one is merged to also add support for owned ones through IMemoryOwner<byte>.

Configuration configuration,
Memory<byte> byteMemory,
int width,
int height,
ImageMetadata metadata)
where TPixel : unmanaged, IPixel<TPixel>
{
Guard.NotNull(configuration, nameof(configuration));
Guard.NotNull(metadata, nameof(metadata));

var memoryManager = new ByteMemoryManager<TPixel>(byteMemory);
var memorySource = MemoryGroup<TPixel>.Wrap(memoryManager.Memory);
return new Image<TPixel>(configuration, memorySource, width, height, metadata);
}

/// <summary>
/// Wraps an existing contiguous memory area of 'width' x 'height' pixels,
/// allowing to view/manipulate it as an ImageSharp <see cref="Image{TPixel}"/> instance.
/// </summary>
/// <typeparam name="TPixel">The pixel type</typeparam>
/// <param name="configuration">The <see cref="Configuration"/></param>
/// <param name="byteMemory">The byte memory representing the pixel data.</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 Image<TPixel> WrapMemory<TPixel>(
Configuration configuration,
Memory<byte> byteMemory,
int width,
int height)
where TPixel : unmanaged, IPixel<TPixel>
=> 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 ImageSharp <see cref="Image{TPixel}"/> instance.
/// The memory is being observed, the caller remains responsible for managing it's lifecycle.
/// </summary>
/// <typeparam name="TPixel">The pixel type.</typeparam>
/// <param name="byteMemory">The byte memory representing the pixel data.</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 Image<TPixel> WrapMemory<TPixel>(
Memory<byte> byteMemory,
int width,
int height)
where TPixel : unmanaged, IPixel<TPixel>
=> WrapMemory<TPixel>(Configuration.Default, byteMemory, width, height);
}
}
67 changes: 67 additions & 0 deletions src/ImageSharp/Memory/ByteMemoryManager{T}.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using System.Runtime.InteropServices;

namespace SixLabors.ImageSharp.Memory
{
/// <summary>
/// A custom <see cref="MemoryManager{T}"/> that can wrap <see cref="Memory{T}"/> of <see cref="byte"/> instances
/// and cast them to be <see cref="Memory{T}"/> for any arbitrary unmanaged <typeparamref name="T"/> value type.
/// </summary>
/// <typeparam name="T">The value type to use when casting the wrapped <see cref="Memory{T}"/> instance.</typeparam>
internal sealed class ByteMemoryManager<T> : MemoryManager<T>
where T : unmanaged
{
/// <summary>
/// The wrapped <see cref="Memory{T}"/> of <see cref="byte"/> instance.
/// </summary>
private readonly Memory<byte> memory;

/// <summary>
/// Initializes a new instance of the <see cref="ByteMemoryManager{T}"/> class.
/// </summary>
/// <param name="memory">The <see cref="Memory{T}"/> of <see cref="byte"/> instance to wrap.</param>
public ByteMemoryManager(Memory<byte> memory)
{
this.memory = memory;
}

/// <inheritdoc/>
protected override void Dispose(bool disposing)
{
}

/// <inheritdoc/>
public override Span<T> GetSpan()
{
if (MemoryMarshal.TryGetArray(this.memory, out ArraySegment<byte> arraySegment))
{
return MemoryMarshal.Cast<byte, T>(arraySegment.AsSpan());
}

if (MemoryMarshal.TryGetMemoryManager<byte, MemoryManager<byte>>(this.memory, out MemoryManager<byte> memoryManager))
{
return MemoryMarshal.Cast<byte, T>(memoryManager.GetSpan());
}

// This should never be reached, as Memory<T> can currently only be wrapping
// either a byte[] array or a MemoryManager<byte> instance in this case.
ThrowHelper.ThrowArgumentException("The input Memory<byte> instance was not valid.", nameof(this.memory));

return default;
}

/// <inheritdoc/>
public override MemoryHandle Pin(int elementIndex = 0)
{
return this.memory.Pin();
}

/// <inheritdoc/>
public override void Unpin()
{
}
}
}
107 changes: 106 additions & 1 deletion tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.PixelFormats;
using Xunit;
Expand Down Expand Up @@ -80,6 +80,52 @@ public override void Unpin()
}
}

public sealed class CastMemoryManager<TFrom, TTo> : MemoryManager<TTo>
where TFrom : unmanaged
where TTo : unmanaged
{
private readonly Memory<TFrom> memory;

public CastMemoryManager(Memory<TFrom> memory)
{
this.memory = memory;
}

/// <inheritdoc/>
protected override void Dispose(bool disposing)
{
}

/// <inheritdoc/>
public override Span<TTo> GetSpan()
{
if (MemoryMarshal.TryGetArray(this.memory, out ArraySegment<TFrom> arraySegment))
{
return MemoryMarshal.Cast<TFrom, TTo>(arraySegment.AsSpan());
}

if (MemoryMarshal.TryGetMemoryManager<TFrom, MemoryManager<TFrom>>(this.memory, out MemoryManager<TFrom> memoryManager))
{
return MemoryMarshal.Cast<TFrom, TTo>(memoryManager.GetSpan());
}

ThrowHelper.ThrowArgumentException("The input Memory<byte> instance was not valid.", nameof(this.memory));

return default;
}

/// <inheritdoc/>
public override MemoryHandle Pin(int elementIndex = 0)
{
return this.memory.Pin();
}

/// <inheritdoc/>
public override void Unpin()
{
}
}

[Fact]
public void WrapMemory_CreatedImageIsCorrect()
{
Expand Down Expand Up @@ -173,6 +219,65 @@ public void WrapSystemDrawingBitmap_WhenOwned()
}
}

[Fact]
public void WrapMemory_FromBytes_CreatedImageIsCorrect()
{
Configuration cfg = Configuration.Default.Clone();
var metaData = new ImageMetadata();

var array = new byte[25 * Unsafe.SizeOf<Rgba32>()];
var memory = new Memory<byte>(array);

using (var image = Image.WrapMemory<Rgba32>(cfg, memory, 5, 5, metaData))
{
Assert.True(image.TryGetSinglePixelSpan(out Span<Rgba32> imageSpan));
ref Rgba32 pixel0 = ref imageSpan[0];
Assert.True(Unsafe.AreSame(ref Unsafe.As<byte, Rgba32>(ref array[0]), ref pixel0));

Assert.Equal(cfg, image.GetConfiguration());
Assert.Equal(metaData, image.Metadata);
}
}

[Fact]
public void WrapSystemDrawingBitmap_FromBytes_WhenObserved()
{
if (ShouldSkipBitmapTest)
{
return;
}

using (var bmp = new Bitmap(51, 23))
{
using (var memoryManager = new BitmapMemoryManager(bmp))
{
Memory<Bgra32> pixelMemory = memoryManager.Memory;
Memory<byte> byteMemory = new CastMemoryManager<Bgra32, byte>(pixelMemory).Memory;
Bgra32 bg = Color.Red;
Bgra32 fg = Color.Green;

using (var image = Image.WrapMemory<Bgra32>(byteMemory, bmp.Width, bmp.Height))
{
Assert.Equal(pixelMemory, image.GetRootFramePixelBuffer().GetSingleMemory());
Assert.True(image.TryGetSinglePixelSpan(out Span<Bgra32> 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);
}
}

private static bool ShouldSkipBitmapTest =>
!TestEnvironment.Is64BitProcess || (TestHelpers.ImageSharpBuiltAgainst != "netcoreapp3.1" && TestHelpers.ImageSharpBuiltAgainst != "netcoreapp2.1");
}
Expand Down