-
-
Notifications
You must be signed in to change notification settings - Fork 853
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
Target Image<TPixel> loading/decoding APIs #1490
Conversation
Co-authored-by: Clinton Ingram <[email protected]>
…geSharp into js/Shuffle3Channel
Fix JpegDecoderTests.Decode_IsCancellable
src/ImageSharp/Image.cs
Outdated
@@ -67,7 +67,7 @@ protected Image(Configuration configuration, PixelTypeInfo pixelType, ImageMetad | |||
public int Height => this.size.Height; | |||
|
|||
/// <inheritdoc/> | |||
public ImageMetadata Metadata { get; } | |||
public ImageMetadata Metadata { get; internal set; } |
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.
Should there be a public API for setting this somewhere? With the new setter being internal
, a third party image decoder can't properly re-use an Image
as it can't change the metadata.
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.
You set individual properties on the metadata not the metadata itself.
See ImageMetadata
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.
I see, that makes sense. Seeing different *Metadata
classes had me confused for a moment, not noticing that ImageMetadata
is sealed.
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.
@Sergio0694 we should rather fill the existing metadata in the decoders. Reasons: (1) We don't want the setter to be public. (2) 3rd party decoders should be able to function the same way our decoders do.
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.
We should decide on API questions before proceeding.
I like the idea of having a Load(Async)
overloads that work on an existing Image<T>
instance over the static variants in the original proposal, since they separate the concerns of wrapping the memory and loading the image. This reduces the number of overloads we need to create and maintain to NumOf(WrapVariants) + NumOf(LoadVariants)
from NumOf(WrapVariants) * NumOf(LoadVariants)
. (The numbers will likely keep growing.) On the other hand, the user needs to know the dimensions in advance, I'm not sure if it's always possible. @DaZombieKiller? (Also see my notes in the very end.)
If we stick with the instance API, we need to decide on the location of the new overloads. Here my preference is to have them as extension methods in either AdvancedImageExtensions
or a new extension class under the Advanced
namespace.
We need to decide if we are ok with the breaking changes of extending IImageDecoder
(break a small number of advanced users to deliver a feature for another group of advanced users ... and kinda also break our promise to not break).
@JimBobSquarePants suggestions?
Right now I just throw an ArgumentException if the image has the wrong size. Do we want to try to mutate it instead?
If the stream image size is bigger, we clearly need to throw. For other cases, I see 3 options here:
- The user wants to change dimensions (mutate)
- The user wants to decode to a sub rectangle of the target image
- The user is expecting an exception if sizes mismatch (current behavior)
src/ImageSharp/Image.FromStream.cs
Outdated
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception> | ||
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> | ||
/// <typeparam name="TPixel">The pixel format.</typeparam> | ||
public static void Load<TPixel>(Stream stream, Image<TPixel> image, IImageDecoder decoder) |
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.
I would either make these instance methods on Image
or extension methods in AdvancedImageExtensions
.
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.
I'd say AdvancedImageExtensions
src/ImageSharp/Image.cs
Outdated
@@ -67,7 +67,7 @@ protected Image(Configuration configuration, PixelTypeInfo pixelType, ImageMetad | |||
public int Height => this.size.Height; | |||
|
|||
/// <inheritdoc/> | |||
public ImageMetadata Metadata { get; } | |||
public ImageMetadata Metadata { get; internal set; } |
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.
@Sergio0694 we should rather fill the existing metadata in the decoders. Reasons: (1) We don't want the setter to be public. (2) 3rd party decoders should be able to function the same way our decoders do.
/// <param name="stream">The <see cref="Stream"/> containing image data.</param> | ||
/// <param name="image">The target <see cref="Image{TPixel}"/> instance.</param> | ||
// TODO: Document ImageFormatExceptions (https://github.com/SixLabors/ImageSharp/issues/1110) | ||
void Decode<TPixel>(Stream stream, Image<TPixel> image) |
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.
This addition is gonna be a breaking change for users who implemented their own IImageDecoder
. The reason @DaZombieKiller proposed IImageMemoryDecoder
was to avoid this.
I'm undecided on this, with a slight preference to not break.
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.
Hmmmm.... It's a fairly fundamental break and there are decoder out in the wild. I say new interface.
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.
In that case, now comes the hard part: naming it. IImageMemoryDecoder
doesn't seem descriptive enough, especially since it's no longer operating on Memory<T>
like in the original proposal. Maybe IImageTargetDecoder
or IImageTargetedDecoder
, based on the name of this PR?
That also gives rise to another open question: What is expected to happen if you attempt to decode into an Image
with a decoder that doesn't support doing that? It could potentially execute a slow path with a copy, but since this is an API intended for high-performance scenarios I think throwing makes more sense.
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.
We'd have to expose the interface and use the type as the method parameter type. Can't call what you don't have then.
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 about the overloads that don't take an IImageDecoder
currently? Would they just be removed?
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.
When someone calls the new Image.Load
without a specified decoder we will loop through our config to find a matching decoder that implements the interface. If there is none it will through.
I generally don't recommend people use specific decoders unless they have to as they skip the format validation checks.
{ | ||
if (this.ImageWidth != image.Width || this.ImageHeight != image.Height) | ||
{ | ||
ThrowHelper.ThrowArgumentException("The input image has an invalid size", nameof(image)); |
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.
ThrowHelper.ThrowArgumentException("The input image has an invalid size", nameof(image)); | |
ThrowHelper.ThrowArgumentException("The input image should match the dimensions of the image in the stream.", nameof(image)); |
Same for the other decoders. In Jpeg we can throw on this much earlier in the decoding workflow.
In my use case they're always known as I just use |
Add PremultiplyAlpha to ResizeOptions
Fix for Issue #1505
…rsion Vectorize Jpeg Encoder Color Conversion
Assembly for loading in the loop went from: ```asm vmovss xmm2, [rax] vbroadcastss xmm2, xmm2 vmovss xmm3, [rax+4] vbroadcastss xmm3, xmm3 vinsertf128 ymm2, ymm2, xmm3, 1 ``` To: ```asm vmovsd xmm3, [rax] vbroadcastsd ymm3, xmm3 vpermps ymm3, ymm1, ymm3 ```
See Vector256.Create issue: dotnet/runtime#47236
Speed improvements to resize kernel (w/ SIMD)
db51f69
to
172c48e
Compare
Prerequisites
Closes #1487
Description
This PR adds new APIs to load/decode images directly into reused
Image<TPixel>
instances.Details
I've decided to add the overloads for the
Image<TPixel>
class to improve interoperability with other APIs.I didn't feel great about either adding a new, separate
IImageMemoryDecoder
type, so this should help with consustency 😄API additions (click to expand):
Use case example
The new APIs allow for eg. GPU processing (assuming DirectX 12 buffers on NUMA architecture) like so (using ComputeSharp):
The big advantage is that we can completely skip one entire copy of the image, as we can use the new APIs to load/decode the image directly into the temporary upload buffer on the GPU. Without these APIs, we'd instead have to load the image into CPU memory, then copy from there to the temporary upload GPU buffer, then copy again from there to the shader visible GPU buffer.
Open questions
❓ Right now I just throw a
NotSupportedException
for GIF files. Is this ok?❓ Right now I just throw an
ArgumentException
if the image has the wrong size. Do we want to try to mutate it instead?