Skip to content

Conversation

@tocsoft
Copy link
Member

@tocsoft tocsoft commented Apr 18, 2020

Prerequisites

  • I have written a descriptive pull-request title
  • I have verified that there are no overlapping pull-requests open
  • I have verified that I am following matches the existing coding patterns and practice as demonstrated in the repository. These follow strict Stylecop rules 👮.
  • I have provided test coverage for my change (where applicable)

Description

This adds a property bag to both the Configuration class and the IImageProcessingContext<T> interface to allow for passing defaults and any other contextual information between calls against the context. Includes an initial implementation for GraphicOptions class to allow setting global blending options etc across the entire application or just the mutation.

This will likely play more into drawing in the future where we will want to be able to define global fallback fonts (enabling default emoji fonts etc), anti-aliasing fill modes etc.

@tocsoft tocsoft requested a review from a team April 18, 2020 16:11
@codecov
Copy link

codecov bot commented Apr 18, 2020

Codecov Report

Merging #1176 into master will increase coverage by 0.02%.
The diff coverage is 96.15%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #1176      +/-   ##
==========================================
+ Coverage   82.47%   82.49%   +0.02%     
==========================================
  Files         690      691       +1     
  Lines       29918    29941      +23     
  Branches     3380     3382       +2     
==========================================
+ Hits        24674    24699      +25     
+ Misses       4542     4540       -2     
  Partials      702      702              
Flag Coverage Δ
#unittests 82.49% <96.15%> (+0.02%) ⬆️
Impacted Files Coverage Δ
...rp/Processing/Processors/Overlays/GlowProcessor.cs 81.81% <ø> (+8.48%) ⬆️
...rocessing/Processors/Overlays/VignetteProcessor.cs 100.00% <ø> (ø)
...p/Processing/Extensions/Overlays/GlowExtensions.cs 83.33% <80.00%> (ø)
...ocessing/Extensions/Overlays/VignetteExtensions.cs 92.00% <80.00%> (ø)
src/ImageSharp/Configuration.cs 100.00% <100.00%> (ø)
src/ImageSharp/GraphicOptionsDefaultsExtensions.cs 100.00% <100.00%> (ø)
...Processing/DefaultImageProcessorContext{TPixel}.cs 100.00% <100.00%> (ø)
...ocessing/Extensions/Drawing/DrawImageExtensions.cs 95.65% <100.00%> (ø)
...ocessing/Extensions/Filters/LomographExtensions.cs 100.00% <100.00%> (ø)
...rocessing/Extensions/Filters/PolaroidExtensions.cs 100.00% <100.00%> (ø)
... and 6 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 6e08eb3...d38764d. Read the comment docs.

Copy link
Member

@antonfirsov antonfirsov left a 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 a several design questions before continuing:

  1. The new concept somewhat overlaps with the way we are using Configuration. When to prefer values in the property bag? When to prefer strongly-typed properties on Configuration?
  2. According to decisions on 1: are there any existing API-s we should change for the sake of consistency in the core library?
  3. Not urgent, since we can wait with API stability, but: Same question for ImageSharp.Drawing. I think this enables composition over inheritance techniques: Have a separate GraphicsOptions and TextOptions classes Instead of TextGraphicsOptions : GraphicsOptions inheritance.
  4. Shouldn't IImageProcessingContext.Properties inherit all values of Configuration.Properties by default?

@tocsoft
Copy link
Member Author

tocsoft commented Apr 19, 2020

I was hoping this would trigger a discussion about this.

We should decide on a several design questions before continuing:

  1. The new concept somewhat overlaps with the way we are using Configuration. When to prefer values in the property bag? When to prefer strongly-typed properties on Configuration?

I can see there being some use cases around encoder and decoder options that might make use of this.

  1. According to decisions on 1: are there any existing API-s we should change for the sake of consistency in the core library?

Only thing to immediately consider is do we remove the overloads that take a GraphicsOptions and instead rely on the .SetGraphicOptions(options) calls to provide any settings. This lends itself to the composition and make TextOptions & ShapeOptions simpler.

  1. Not urgent, since we can wait with API stability, but: Same question for ImageSharp.Drawing. I think this enables composition over inheritance techniques: Have a separate GraphicsOptions and TextOptions classes Instead of TextGraphicsOptions : GraphicsOptions inheritance.
  1. Shouldn't IImageProcessingContext.Properties inherit all values of Configuration.Properties by default?

The way I setup the GetDefaultGraphicsOptions() was to fallback to the Configuration, but it could be simpler to just clone the Configurations options functionally its currently the same but there are edge cases that make it subtly different.

@antonfirsov
Copy link
Member

antonfirsov commented Apr 19, 2020

The way I setup the GetDefaultGraphicsOptions() was to fallback to the Configuration

But in that case you need to do the same steps manually for other properties, I'd rather prefer to solve this by design, if possible.

Only thing to immediately consider is do we remove the overloads that take a GraphicsOptions and instead rely on the .SetGraphicOptions(options) calls to provide any settings.

Can we have a quick consumer-side API comparison? How does a sample drawing mutation call using GraphicsOptions look now VS how would it look with .SetGraphicOptions(options)?

@antonfirsov
Copy link
Member

antonfirsov commented Apr 19, 2020

I can see there being some use cases around encoder and decoder options that might make use of this.

Currently, we configure encoders/decoders with Configuration-wide IImageEncoder/IImageDecoder instances. I doubt it makes sense to store some properties in the bag, while storing others in codec instance members, we should pick one approach.

@tocsoft
Copy link
Member Author

tocsoft commented Apr 19, 2020

img.Mutate(c => c
    .Vignette(new GraphicsOptions
    {
        AlphaCompositionMode = PixelAlphaCompositionMode.Clear,
        ColorBlendingMode = PixelColorBlendingMode.Darken
    }, Color.Red)
    .Vignette(new GraphicsOptions
    {
        AlphaCompositionMode = PixelAlphaCompositionMode.Clear,
        ColorBlendingMode = PixelColorBlendingMode.Lighten
    }, Color.Green)
    .Vignette(new GraphicsOptions
    {
        AlphaCompositionMode = PixelAlphaCompositionMode.Clear,
        ColorBlendingMode = PixelColorBlendingMode.Multiply
    }, Color.Blue)
);

vs using SetGraphicsOptions

img.Mutate(c => c
    .SetGraphicsOptions(new GraphicsOptions
    {
        AlphaCompositionMode = PixelAlphaCompositionMode.Clear,
        ColorBlendingMode = PixelColorBlendingMode.Darken
    })
    .Vignette(Color.Red)
    .SetGraphicsOptions(o => o.ColorBlendingMode = PixelColorBlendingMode.Lighten)
    .Vignette(Color.Green)
    .SetGraphicsOptions(o => o.ColorBlendingMode = PixelColorBlendingMode.Multiply) // alpha still set to Clear
    .Vignette(Color.Blue)
);

@JimBobSquarePants
Copy link
Member

I need to have a proper think about this. I'm not sure why GraphicsOptions should be treated any differently to other options types. (ResizeOptions for example). Yes, they can be reused for different methods but they a still per-operation options.

@tocsoft
Copy link
Member Author

tocsoft commented Apr 20, 2020

don't worry about it too much... it was just a thought really, we'll leave the overloads on that take an options too.

We definitely need/want the extensibility to load defaults and pass global parameters otherwise Drawing can't add global config options.

Copy link
Member

@JimBobSquarePants JimBobSquarePants left a comment

Choose a reason for hiding this comment

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

Ok. So I like the idea of an extensible property bag in the configuration since that lets us extend Configuration without breaking the API.

I don't think we need the additional bag in the context though. If we want something global it I believe it should be stored in Configuration instances to prevent ambiguity. Individual processors can accept options as parameters, falling back to the Configuration property bag and extension methods when not available.

@tocsoft
Copy link
Member Author

tocsoft commented Apr 21, 2020

I really want to keep the version on context too as it opens up a world of options.. I will want to leverage it more on the drawing side and wanted to get it in now.
I don't think it harms anything and only give us flexibility without having to orchestrate multi repo releases especially after RC/1.0.

@JimBobSquarePants
Copy link
Member

I really want to keep the version on context too as it opens up a world of options.. I will want to leverage it more on the drawing side and wanted to get it in now.

I don't understand the use case.

The Configuration class and it's property bag would already be available to the context and processors either by direct injection or from the source image. I don't get what the additional property collection offers?

@tocsoft
Copy link
Member Author

tocsoft commented Apr 21, 2020

Configuration is a system wide global... or should be.

ImageProcessingContext is explicitly a single processing run on a single image.

Its the same difference as there being a Headers collection no HttpClient and a separate one on HttpRequestMessage one if for globally shared values one is for unit of work wide values.

Main use case is on processing SVGs where we want to be able to record and apply transforms and masks on parts of a processing tree and then revert back later without having to pass loads of complex options around..... not everything in the tree will even need to know about the setting from higher up it would just work and compose into the correct setting. We shouldn't really be encouraging people to create new configuration instances all the time everything should just compose and work. Your point of reference for the API at the moment is based on the straight processor base manipulations that are mostly independent of each other with Drawing they are all variants and interaction on the same underlying processor, the way you want to use the API is different... You don't want to have to call a transform on every single vector on the way thru every time you want to be able to do something like SetOrigin(xxx), SetTransform(yyy) and it to just apply to all the calls after that point, but you don't want those to apply to this 2nd image for this 2nd request.

@JimBobSquarePants
Copy link
Member

Configuration is considered global yes but we explicitly added the ability to pass instances to individual or chained processors as part of #650

ImageProcessingContext is a single context that can be applied to multiple methods, not just one. This allows sharing of the options but not the fine grained options like you are suggesting.

using (var image = new Image<Rgba32>(100, 100))
{
    var contextConfig = new Configuration();
    image.Mutate(
        contextConfig,
         x => x.Hue(180)
               .Pixelate(4)
               .Saturate(.75F)
               .Vignette());
}

So I see now where you are coming from now API-wise. It's definitely nice to set the options via an action than by splitting up or constructing individual options. SetGraphicsOptions() is neat and allows much reduced boilerplate. The idividual property bag works well for this.

Do we need the GetGraphicsOptions() methods though since we always have access to the options via the action context for setting?

@tocsoft
Copy link
Member Author

tocsoft commented Apr 21, 2020

Do we need the GetGraphicsOptions() methods though since we always have access to the options via the action context for setting?

Yeah we do.

Without the GetGraphicsOptions() every call site that needs the defaults will need to have the same logic repeated for try getting it from context, oh its not there lets try the try getting it from config instead, still not there let allocate a new instance. (we also no no longer need to allocate every time too as we cache the config wide version.

Copy link
Member

@JimBobSquarePants JimBobSquarePants left a comment

Choose a reason for hiding this comment

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

Yep, you've convinced me. This is a useful and powerful addition.

Copy link
Member

@antonfirsov antonfirsov left a comment

Choose a reason for hiding this comment

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

Parameter naming + some further points to consider.

/// Gets a set of properties for the Image Processing Context.
/// </summary>
/// <remarks>This can be used for storing global settings and defaults to be accessable to processors.</remarks>
IDictionary<object, object> Properties { get; }
Copy link
Member

Choose a reason for hiding this comment

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

I wonder if we want to consider something more strongly-typed here?

  • System.Type or a custom object as a key
  • Restrictions for the value: do we expect simple values here? If not, maybe a marker interface.
  • Our own types GraphicsProperties / IGraphicsProperties instead of IDictionary<K,V>

Copy link
Member Author

Choose a reason for hiding this comment

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

no it should be explicitly be as loosely typed as possible and we should not be limiting anything from being but in the property bag, that's why I even when as far as using <object,object> rather than <string,object> to allow developers to use it for there needs they can do anything with it... use strings, use private object instances (no way for someone to accidently reuse the key) or anything in between... we would be loosing one of the key flexibility benefits of this extension point is we make the type a custom type with restrictions..

return go;
}

var configOptions = context.Configuration.GetGraphicsOptions();
Copy link
Member

@antonfirsov antonfirsov Apr 21, 2020

Choose a reason for hiding this comment

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

Are we really sure we want to repeat the defaulting logic in all the Get*** extension methods, instead of populating Properties in the DefaultImageProcessingContext ctr?

What are the pros for this decision?

Copy link
Member

Choose a reason for hiding this comment

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

Ah yeah... If our keys are strongly typed that makes comparison faster and easier too.

Copy link
Member

Choose a reason for hiding this comment

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

FYI an interesting API proposal that also contains the concept of property bags: dotnet/runtime#1793

In total, a bit too verbose for my style, but I like the idea of having custom types for the bag, and (in some form) for the keys. I would also apply it for values, if it makes sense.

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 do kinda like the pattern proposed in the runtime but, (and it is a big but) it feels soo overengineered, sometimes (and in my experience usually) the simple solution is the right one.

@tocsoft tocsoft merged commit afcb3ca into master Apr 22, 2020
@tocsoft tocsoft deleted the sw/property-bag branch April 22, 2020 15:37
@JimBobSquarePants JimBobSquarePants added this to the 1.0.0-rc1 milestone Apr 24, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants