Image.WrapMemory<TPixel> APIs wrapping Memory<byte>#1314
Image.WrapMemory<TPixel> APIs wrapping Memory<byte>#1314JimBobSquarePants merged 15 commits intomasterfrom
Conversation
| /// <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.
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?
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
No preference, other than it's always easier to review multiple smaller (subsequent) PR-s.
There was a problem hiding this comment.
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>.
|
@JimBobSquarePants my build is failing locally due to a bunch of errors about a missing assembly key in Is there some new additional configuration step I'm missing? Thanks! |
|
@Sergio0694 Likely |
|
@JimBobSquarePants I had already tried to clean the solution and to run both |
|
@JimBobSquarePants Well surprise, I disabled the .NET 5 Preview SDK and I can build just fine now 😅 |
How do you disable a preview? I've been terrified to install any of them. |
|
@JimBobSquarePants It's pretty easy actually, you need to go to Tools > Options > Environment > Preview Features and then untick the option regarding preview versions of the .NET Core SDK, and restart VS: I could finally debug the tests locally with that out of the picture, fixed crossed for the CI now! 🚀 |
Codecov Report
@@ Coverage Diff @@
## master #1314 +/- ##
==========================================
- Coverage 82.77% 82.77% -0.01%
==========================================
Files 689 690 +1
Lines 30956 30973 +17
Branches 3511 3511
==========================================
+ Hits 25625 25639 +14
- Misses 4610 4613 +3
Partials 721 721
Flags with carried forward coverage won't be shown. Click here to find out more.
Continue to review full report at Codecov.
|
|
CI passed! 🚀 The PR should be ready for review with the original API surface mentioned in the linked issue. |
antonfirsov
left a comment
There was a problem hiding this comment.
Just a few quick remarks, will do deeper review later.
| /// <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.
No preference, other than it's always easier to review multiple smaller (subsequent) PR-s.
|
@Sergio0694 I will get to this. Just fixing a critical issue #1316 first |
|
@JimBobSquarePants Don't worry, no rush! 😄 |
|
@JimBobSquarePants Just noticed this bit while adding the unit tests: ImageSharp/src/ImageSharp/Memory/MemoryOwnerExtensions.cs Lines 16 to 22 in 801961d Wouldn't it be more efficient to just have |
|
@Sergio0694 Wow! Never actually looked at the code for |
## PR Type What kind of change does this PR introduce? <!-- Please uncomment one or more that apply to this PR. --> - Feature ## What is the current behavior? <!-- Please describe the current behavior that you are modifying, or link to a relevant issue. --> Right now there is no (easy) way to cast a `Memory<TFrom>` instance to a `Memory<TTo>` instance. There are APIs to to do that for `Span<T>` instances, but not for `Memory<T>`. The reason for that is that with a `Span<T>` it's just a matter of retrieving the wrapped reference, reinterpreting it and then adjusting the size, then creating a new `Span<T>` instance. But a `Memory<T>` instance is completely different: it wraps an object which could be either a `T[]` array, a `MemoryManager<T>` instance, etc. The result is that currently there are no APIs in the BCL nor in the toolkit to just "cast" a `Memory<T>`. This feature has been requested by a number of developers, including in a well known library such as `ImageSharp`: > Yes, that's exactly what I would need. But I'm wondering how would you implement it. > It's certainly non trivial to cast a `Memory<byte>` to a `Memory<TPixel>` and if there's an API for that I would gladly want to know... > So I pressume `ImageSharp` would need to do some work under the hood. (_`ImageSharp` issue, [here](SixLabors/ImageSharp#1097 (comment)) To solve that, I created a very simplified version of the code included in this PR, into a PR [here](SixLabors/ImageSharp#1314). Having this available right out of the box in the `HighPerformance` package would be helpful in a number of similar situations, especially with `Memory<T>` APIs becoming more and more common across libraries now (as they've been out for a while). ## What is the new behavior? <!-- Describe how was this issue resolved or changed? --> This PR includes 4 new extensions for the `Memory<T>` and `ReadOnlyMemory<T>` types that enable the following: ```csharp // Cast between two Memory<T> instances... Memory<byte> memoryOfBytes = new byte[128].AsMemory(); Memory<float> memoryOfFloats = memoryOfBytes.Cast<byte, float>(); // ...any number of times is needed Memory<int> memoryOfInts = memoryOfFloats.Cast<float, int>(); Memory<byte> backToBytesMemory = memoryOfInts.Cast<int, byte>(); // Or just convert into bytes directly Memory<int> sourceAsInts = new int[128].AsMemory(); Memory<byte> sourceAsBytes = sourceAsInts.AsBytes(); // Want to get a stream from a string? Why not! 😄 using (Stream stream = "Hello world".AsMemory().AsBytes().AsStream()) { // Use the stream here, which reads *directly* from the string data! } ``` Here is the full list of the new APIs introduced in this PR: ```csharp namespace Microsoft.Toolkit.HighPerformance.Extensions { public static class MemoryExtensions { public static Memory<byte> AsBytes<T>(this Memory<T> memory) where T : unmanaged; public static Memory<TTo> Cast<TFrom, TTo>(this Memory<TFrom> memory) where TFrom : unmanaged where TTo : unmanaged; } public static class ReadOnlyMemoryExtensions { public static ReadOnlyMemory<byte> AsBytes<T>(this ReadOnlyMemory<T> memory) where T : unmanaged; public static ReadOnlyMemory<TTo> Cast<TFrom, TTo>(this ReadOnlyMemory<TFrom> memory) where TFrom : unmanaged where TTo : unmanaged; } } ``` ## Notes Marking as draft as this is still being worked on, but feedbacks and reviews are welcome! 😄 ## PR Checklist Please check if your PR fulfills the following requirements: - [X] Tested code with current [supported SDKs](../readme.md#supported) - [ ] ~~Pull Request has been submitted to the documentation repository [instructions](..\contributing.md#docs). Link: <!-- docs PR link -->~~ - [ ] ~~Sample in sample app has been added / updated (for bug fixes / features)~~ - [ ] ~~Icon has been created (if new sample) following the [Thumbnail Style Guide and templates](https://github.com/windows-toolkit/WindowsCommunityToolkit-design-assets)~~ - [X] Tests for the changes have been added (for bug fixes / features) (if applicable) - [X] Header has been added to all new source files (run *build/UpdateHeaders.bat*) - [X] Contains **NO** breaking changes
## PR Type What kind of change does this PR introduce? <!-- Please uncomment one or more that apply to this PR. --> - Feature ## What is the current behavior? <!-- Please describe the current behavior that you are modifying, or link to a relevant issue. --> Right now there is no (easy) way to cast a `Memory<TFrom>` instance to a `Memory<TTo>` instance. There are APIs to to do that for `Span<T>` instances, but not for `Memory<T>`. The reason for that is that with a `Span<T>` it's just a matter of retrieving the wrapped reference, reinterpreting it and then adjusting the size, then creating a new `Span<T>` instance. But a `Memory<T>` instance is completely different: it wraps an object which could be either a `T[]` array, a `MemoryManager<T>` instance, etc. The result is that currently there are no APIs in the BCL nor in the toolkit to just "cast" a `Memory<T>`. This feature has been requested by a number of developers, including in a well known library such as `ImageSharp`: > Yes, that's exactly what I would need. But I'm wondering how would you implement it. > It's certainly non trivial to cast a `Memory<byte>` to a `Memory<TPixel>` and if there's an API for that I would gladly want to know... > So I pressume `ImageSharp` would need to do some work under the hood. (_`ImageSharp` issue, [here](SixLabors/ImageSharp#1097 (comment)) To solve that, I created a very simplified version of the code included in this PR, into a PR [here](SixLabors/ImageSharp#1314). Having this available right out of the box in the `HighPerformance` package would be helpful in a number of similar situations, especially with `Memory<T>` APIs becoming more and more common across libraries now (as they've been out for a while). ## What is the new behavior? <!-- Describe how was this issue resolved or changed? --> This PR includes 4 new extensions for the `Memory<T>` and `ReadOnlyMemory<T>` types that enable the following: ```csharp // Cast between two Memory<T> instances... Memory<byte> memoryOfBytes = new byte[128].AsMemory(); Memory<float> memoryOfFloats = memoryOfBytes.Cast<byte, float>(); // ...any number of times is needed Memory<int> memoryOfInts = memoryOfFloats.Cast<float, int>(); Memory<byte> backToBytesMemory = memoryOfInts.Cast<int, byte>(); // Or just convert into bytes directly Memory<int> sourceAsInts = new int[128].AsMemory(); Memory<byte> sourceAsBytes = sourceAsInts.AsBytes(); // Want to get a stream from a string? Why not! 😄 using (Stream stream = "Hello world".AsMemory().AsBytes().AsStream()) { // Use the stream here, which reads *directly* from the string data! } ``` Here is the full list of the new APIs introduced in this PR: ```csharp namespace Microsoft.Toolkit.HighPerformance.Extensions { public static class MemoryExtensions { public static Memory<byte> AsBytes<T>(this Memory<T> memory) where T : unmanaged; public static Memory<TTo> Cast<TFrom, TTo>(this Memory<TFrom> memory) where TFrom : unmanaged where TTo : unmanaged; } public static class ReadOnlyMemoryExtensions { public static ReadOnlyMemory<byte> AsBytes<T>(this ReadOnlyMemory<T> memory) where T : unmanaged; public static ReadOnlyMemory<TTo> Cast<TFrom, TTo>(this ReadOnlyMemory<TFrom> memory) where TFrom : unmanaged where TTo : unmanaged; } } ``` ## Notes Marking as draft as this is still being worked on, but feedbacks and reviews are welcome! 😄 ## PR Checklist Please check if your PR fulfills the following requirements: - [X] Tested code with current [supported SDKs](../readme.md#supported) - [ ] ~~Pull Request has been submitted to the documentation repository [instructions](..\contributing.md#docs). Link: <!-- docs PR link -->~~ - [ ] ~~Sample in sample app has been added / updated (for bug fixes / features)~~ - [ ] ~~Icon has been created (if new sample) following the [Thumbnail Style Guide and templates](https://github.com/windows-toolkit/WindowsCommunityToolkit-design-assets)~~ - [X] Tests for the changes have been added (for bug fixes / features) (if applicable) - [X] Header has been added to all new source files (run *build/UpdateHeaders.bat*) - [X] Contains **NO** breaking changes


Prerequisites
Closes #1097
Description
This PR adds the following APIs to the
Imageclass:The internal implementation includes a new
ByteMemoryManager<T>type that acts as a bridge to wrapMemory<byte>instances and use that data to produce aMemory<TPixel>instance that can be used to load the image as requested.