-
-
Notifications
You must be signed in to change notification settings - Fork 855
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Image.WrapMemory<TPixel> APIs wrapping Memory<byte> #1314
Changes from all commits
3984e80
b3f8572
e26e57f
32380d2
eb22996
2383245
85f91f6
ff5e301
fa59a73
1de45b7
567f3a6
9295e93
d7380f0
d861805
26b948d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,7 +17,7 @@ public abstract partial class Image | |
{ | ||
/// <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. | ||
/// allowing to view/manipulate it as an <see cref="Image{TPixel}"/> instance. | ||
/// </summary> | ||
/// <typeparam name="TPixel">The pixel type</typeparam> | ||
/// <param name="configuration">The <see cref="Configuration"/></param> | ||
|
@@ -38,14 +38,15 @@ public static Image<TPixel> WrapMemory<TPixel>( | |
{ | ||
Guard.NotNull(configuration, nameof(configuration)); | ||
Guard.NotNull(metadata, nameof(metadata)); | ||
Guard.IsTrue(pixelMemory.Length == width * height, nameof(pixelMemory), "The length of the input memory doesn't match the specified image size"); | ||
|
||
var memorySource = MemoryGroup<TPixel>.Wrap(pixelMemory); | ||
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. | ||
/// allowing to view/manipulate it as an <see cref="Image{TPixel}"/> instance. | ||
/// </summary> | ||
/// <typeparam name="TPixel">The pixel type</typeparam> | ||
/// <param name="configuration">The <see cref="Configuration"/></param> | ||
|
@@ -64,7 +65,7 @@ public static Image<TPixel> WrapMemory<TPixel>( | |
|
||
/// <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. | ||
/// 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. | ||
/// </summary> | ||
/// <typeparam name="TPixel">The pixel type.</typeparam> | ||
|
@@ -81,7 +82,7 @@ public static Image<TPixel> WrapMemory<TPixel>( | |
|
||
/// <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. | ||
/// allowing to view/manipulate it as an <see cref="Image{TPixel}"/> instance. | ||
/// The ownership of the <paramref name="pixelMemoryOwner"/> is being transferred to the new <see cref="Image{TPixel}"/> instance, | ||
/// meaning that the caller is not allowed to dispose <paramref name="pixelMemoryOwner"/>. | ||
/// It will be disposed together with the result image. | ||
|
@@ -105,14 +106,15 @@ public static Image<TPixel> WrapMemory<TPixel>( | |
{ | ||
Guard.NotNull(configuration, nameof(configuration)); | ||
Guard.NotNull(metadata, nameof(metadata)); | ||
Guard.IsTrue(pixelMemoryOwner.Memory.Length == width * height, nameof(pixelMemoryOwner), "The length of the input memory doesn't match the specified image size"); | ||
|
||
var memorySource = MemoryGroup<TPixel>.Wrap(pixelMemoryOwner); | ||
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. | ||
/// allowing to view/manipulate it as an <see cref="Image{TPixel}"/> instance. | ||
/// The ownership of the <paramref name="pixelMemoryOwner"/> is being transferred to the new <see cref="Image{TPixel}"/> instance, | ||
/// meaning that the caller is not allowed to dispose <paramref name="pixelMemoryOwner"/>. | ||
/// It will be disposed together with the result image. | ||
|
@@ -134,7 +136,7 @@ public static Image<TPixel> WrapMemory<TPixel>( | |
|
||
/// <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. | ||
/// allowing to view/manipulate it as an <see cref="Image{TPixel}"/> instance. | ||
/// The ownership of the <paramref name="pixelMemoryOwner"/> is being transferred to the new <see cref="Image{TPixel}"/> instance, | ||
/// meaning that the caller is not allowed to dispose <paramref name="pixelMemoryOwner"/>. | ||
/// It will be disposed together with the result image. | ||
|
@@ -150,5 +152,73 @@ 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 <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>( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sounds like a preference to me 😄 |
||
Configuration configuration, | ||
Memory<byte> byteMemory, | ||
int width, | ||
int height, | ||
Sergio0694 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
ImageMetadata metadata) | ||
where TPixel : unmanaged, IPixel<TPixel> | ||
{ | ||
Guard.NotNull(configuration, nameof(configuration)); | ||
Guard.NotNull(metadata, nameof(metadata)); | ||
|
||
var memoryManager = new ByteMemoryManager<TPixel>(byteMemory); | ||
|
||
Guard.IsTrue(memoryManager.Memory.Length == width * height, nameof(byteMemory), "The length of the input memory doesn't match the specified image size"); | ||
|
||
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 <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 <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); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
// Copyright (c) Six Labors. | ||
// Licensed under the Apache License, Version 2.0. | ||
using System; | ||
using System.Buffers; | ||
using System.Runtime.CompilerServices; | ||
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() | ||
{ | ||
Sergio0694 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return MemoryMarshal.Cast<byte, T>(this.memory.Span); | ||
} | ||
|
||
/// <inheritdoc/> | ||
public override MemoryHandle Pin(int elementIndex = 0) | ||
{ | ||
// We need to adjust the offset into the wrapped byte segment, | ||
// as the input index refers to the target-cast memory of T. | ||
// We just have to shift this index by the byte size of T. | ||
return this.memory.Slice(elementIndex * Unsafe.SizeOf<T>()).Pin(); | ||
} | ||
|
||
/// <inheritdoc/> | ||
public override void Unpin() | ||
{ | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remind me to do a pass one day to remove all these guard allocations.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wait what allocations? 🤔
Also do you want me to create an issue about this to make sure we don't forget in the future?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thought that would get you thinking. We always allocate the string whether true or false.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What string though? Both of those are constants, so the JIT will just load the address here.
I feel like I might be missing something obvious 😅
Eg. if you have this (full repro here):
You get this:
So you only pay the two indirect memory accesses to load the strings, but you have no allocations.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah yeah, I'm thinking when we interpolate (which I thought we were doing here). It might seem useful but it adds a lot of overhead