-
-
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
Refactored Mutate/Clone APIs #275
Conversation
…ants/ImageSharp into tocsoft/mutate-api # Conflicts: # src/ImageSharp/ApplyProcessors.cs
Codecov Report
@@ Coverage Diff @@
## master #275 +/- ##
==========================================
+ Coverage 86.71% 86.92% +0.21%
==========================================
Files 671 716 +45
Lines 29912 30507 +595
Branches 2138 2151 +13
==========================================
+ Hits 25937 26517 +580
- Misses 3316 3325 +9
- Partials 659 665 +6
Continue to review full report at Codecov.
|
src/ImageSharp/ApplyProcessors.cs
Outdated
/// <typeparam name="TPixel">The pixel format.</typeparam> | ||
/// <param name="source">The image to rotate, flip, or both.</param> | ||
/// <param name="operations">The operations to perform on the source.</param> | ||
public static void Mutate<TPixel>(this Image<TPixel> source, Action<IImageOperations<TPixel>> operations) |
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.
Question : should theses be extension methods or exposed instance methods of Image<TPixel>
?
The more I think about it I feel these should actually be moved onto Image directly thus exposing this APIs without needing to add the using statements.
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 be consistent:
- Either by redesigning our whole API to be fully extension method focused, so not only
Mutate()
, but all the methods/properties producing derived results from a narrow core API, should be extension methods. So evenimage.Bounds
would become animage.Bounds()
extension method. - Or just turn both
Mutate(operations)
andClone(operations)
to instance methods as well
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.
Bounds() should be an extension method I feel, as its only really used as a helper for image operations. (in fact it should probably just be internal)
We should also probably move out all all the overloads of Save()
into extension methods too Image
would just have Save(Stream, Encoder)
.
Also things like To<TPixel2>()
could also probably be a candidate for changing.
And now the ApplyProcessor()
is basically redundant.
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.
Made a quick review focusing on core stuff.
Haven't checked the concrete processor extension methods. It should be done probably by someone who has better understanding of the processors ;)
where TPixel : struct, IPixel<TPixel>; | ||
} | ||
|
||
/// <summary> |
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.
The naming seems inconsistent here.
It should be probably IImageOperationsFactory
+ CreateImageOperations()
, or IImageOperationsProvider
+ GetImageOperations()
.
src/ImageSharp/ImageOperations.cs
Outdated
/// </summary> | ||
/// <param name="processors">Processors to apply</param> | ||
/// <returns>this </returns> | ||
public IImageOperations<TPixel> ApplyProcessors(IEnumerable<IImageProcessor<TPixel>> processors) |
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 method is unused.
src/ImageSharp/ApplyProcessors.cs
Outdated
where TPixel : struct, IPixel<TPixel> | ||
{ | ||
Guard.NotNull(operations, nameof(operations)); | ||
var generated = new Image<TPixel>(source); |
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.
For most processors we (eg. Resize()
) we are allocating a new Width
*Height
image area twice!
- First calling this copy constructor
- Second time in the processor itself
So the issue Eric Mellino pointed out on gitter is still here, having two allocations is unnecessary. We should find a way to avoid this during this API validation process. I'm not saying that we should refactor everything now, but we need to make sure it is possible with the new API!
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.
The internal parts of the should be refactorable to make it possible to prevent the double allocation.
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'm not sure if it's possible without changing IImageProcessor<TPixel>
design.
The processors doing allocation of a new image buffer should be able to take the source and destination image buffers from the outside instead. It would make SwapPixelBuffers()
unnecessary:
https://github.com/SixLabors/ImageSharp/blob/master/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs#L97
@JimBobSquarePants thoughts?
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 may play around with this, making a proposal in the weekend.
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 was planning to create a new internal interface ICloneableImageProcessor<T> : IImageProcessor<TPixel>
(or similare ) then in the DefaultImageOperator
check if the passed in IImageProcessor
is in fact an ICloneableImageProcessor
and if it is then instead of calling IImageProcessor.Apply()
then it instead call IImageProcessor.CloneAndApply()
which returns the cloned image. All it would require is the DefaultImageOperator
cloning the source image lazerly the first time we encounter an IImageProcesssor
and its not an ICloneableImageProcessor
and if it is on then it will retain the cloned image and just call Apply with it on all remaining IImageProcesssor
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.
feel free to have a go... basically my ideal situation (for v1) is to do it without having to change the public API any, even if that means that only our internal processors have to extra power of generating the clone.
@JimBobSquarePants @tocsoft I don't think any of our users are implementing their own processors though, so probably a minor change on that API somewhere between the beta and the RC should not be an issue. Thoughts? :) |
@antonfirsov c421e6d is basically what I was thinking with regards the fix for double cloning... none of those changes actually alter the public API... so it is possible without having to change |
/// An interface to queue up image operations. | ||
/// </summary> | ||
/// <typeparam name="TPixel">The pixel format</typeparam> | ||
public interface IImageProcessorApplicator<TPixel> |
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.
Shouldn't we find a less verbose name for this? How about IImageProcessingContext<TPixel>
? //image.Mutate(ctx => ctx.Resize(..) )
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 like IImageProcessingContext
/// Encapsulates methods to alter the pixels of an image. | ||
/// </summary> | ||
/// <typeparam name="TPixel">The pixel format.</typeparam> | ||
public interface ICloningImageProcessor<TPixel> : IImageProcessor<TPixel> |
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.
Wasn't your intention to internalize this?
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.
yep i was, must have missed it
/// Allows the application of processors to images. | ||
/// </summary> | ||
/// <typeparam name="TPixel">The pixel format.</typeparam> | ||
internal abstract class CloneingImageProcessor<TPixel> : IImageProcessor<TPixel>, ICloningImageProcessor<TPixel> |
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.
Typo: Cloneing...
Can't we avoid code duplication by subclassing ImageProcessor<TPixel>
?
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.
there is no duplication really, they both follow the same patterns but the code is different, one takes 2 images the other takes 1. Basically CloneingImageProcessor
is for image processors that require a 2nd buffer to apply changes to before overwriting the pixel data in the original. ImageProcessor
if for processors that don't require the 2nd buffer and can apply there changes directly to the source.
|
@JimBobSquarePants @tocsoft tried to clarify the avatar example by adding some docs, comments + a variant, that does the trick without @JimBobSquarePants if you say that users who are not messed with 200 git changes+flu should understand all this, I believe you! 😄 |
Those updates look good to me 👍 I like that we're showing both methods as its demonstrating that we aren't prescribing doing it one way or the other. |
Here is the only problem left:
|
I could use this as the base branch for doing the package renames/namespace changes. That should cause less disruption on people who inadvertently update to latest as they would be having to to pull in a different package anyway. Also means there is only one large braking change instead of 2. |
obviously would still require a blog post but it would be a single one with API, package name in one. |
👍 Good thinking! |
Update namespaces & package names
Prerequisites
Description
This is a rather significatant change to our public APIs the proposal is thus.
Instead of
Image<TPixel>
having a range of extension methods on it to do all sorts of mutations that return the save instance I've introducedIImageOperations<TPixel>
which is used to queue/indirecty applyIImageProcessors
to the source image.The way you get a handle on an
IImageOperations<TPixel>
is by using the only 2 remaining extension APIs fromImage<TPixel>
(should theses be moved directly ontoImage<TPixel>
instead of being extension methds?).The driver for this is I wanted the mutateing APIs to be obvious that they are a fluent chaining/mutating set of operations that return the source image again instead of returning a image each time.
Additionally I wanted to enable a version of all the
ImageOperation
s that did return a new instance for those times you wanted to generate multiple outputs from the same input.So some code examples.
So to perform a simple set of operation you now do this:
this will mutate the pixel data stored in
image
first making it grayscale, then resizing it, and finaly pixelatting it.now to do all that again by not alter the source image would be a simple as
second example has left
image
in exactly the same unaltered state is was in before starting, and instead has created a new imagenew_image
with all the mutations applied.Fixes #265