Skip to content
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

Expose ImageProcessor internals to unblock externalizing ImageSharp.Drawing #967

Closed
7 of 8 tasks
antonfirsov opened this issue Aug 11, 2019 · 12 comments
Closed
7 of 8 tasks
Labels
Milestone

Comments

@antonfirsov
Copy link
Member

antonfirsov commented Aug 11, 2019

Plans to externalize SixLabors.ImageSharp.Drawing

The main library, SixLabors.ImageSharp almost reached Release Candidate quality, however we realized, that it will take much more time to bring SixLabors.ImageSharp.Drawing and it's base libraries SixLabors.Shapes and SixLabors.Fonts to the same level.

In order to unblock the release of ImageSharp, we decided to externalize SixLabors.ImageSharp.Drawing. It shall be:

  • Versioned independently
  • Moved to a separate GitHub repository

We are afraid that Drawing, Shapes, Fonts will reach RC status only after the 1.0 release of the main library.

Blockers

Unfortunately, externalizing ImageSharp.Drawing is not a trivial job, since it depends on certain internals of the main library by using [InternalsVisibleTo(...)], namely:

  • Buffer2D<T>
  • ImageProcessor<T>
  • PixelBlender<T> and PixelOperations<T>.GetPixelBlender(...)
  • IImageVisitor and Image.AcceptVisitor(...)
  • ParallelExecutionSettings, configuration.GetParallelSettings() and ParallelHelper

These issues are actually well-known to anyone trying to implement Image Processors, and complaining about classes being internal. (Hi there @Sergio0694 😄!) This turns the issue of ImageSharp.Drawing into an API-cleanup problem.

Suggested solution

  • Expose Buffer2D<T> with a minimal subset of public methods. Keep MemorySource<T> hidden. Consider reintroducing IBuffer2D<T>.
  • Expose ImageProcessor<T> but consider refactoring the "processor application" logic. See follow-up explanation.
  • Expose PixelBlender<T> and PixelOperations<T>.GetPixelBlender(...)
  • Move IImageVisitor to the Advanced subnamespace, expose IImageVisitor and Image.AcceptVisitor(...)
  • Find out what to do with the rest 😄. Personally, I'm not sure if we should encourage parallel pixel processing. It might be better+easier to keep that logic hidden, and replicate it in the Drawing library.

Proposal to refactor current ImageProcessor logic

IQuantizer is a good example for the interaction and lifecycle rules of non-generic (pixel agnostic) objects and their generic (pixel-specific) counterparts:

I think we should fully adapt this pattern for Image Processors:

  • BeforeImageApply should be replaced with constructor logic: IImageProcessor.CreatePixelSpecificProcessor() should take Image<T> and a Rectangle which is then passed to ImageProcessor<T> constructor, initialization and resource allocation could be done here.
  • AfterImageApply should be replaced with Dispose() (to clean up temporary resources)
  • Image<T>, SourceRectangle and Configuration can be members
  • IImageProcessor<TPixel>.Apply() should be non-parametric. Consider renaming it to Execute()

UPDATE: I no longer think that constructor/Dispose are replacements for Before/After ImageApply. Those should exist side-by side.

UPDATE 2:

Further, "nice to have" points

  • Review SourceRectangle (and maybe TargetRectangle) behavior. See: comments
  • Review IImageProcessor vs ICloningImageProcessor behavior. Expose (I)CloningImageProcessor

UPDATE 3:

  • Expose PixelOperations<T>.To/FromVector4(...) (nice to have, currently not used by Drawing)

To avoid a longer delay, I'm fine if we can't address them within the current API cleanup process, since these are no trivial topics.

@Sergio0694 Sergio0694 mentioned this issue Aug 19, 2019
13 tasks
@tocsoft
Copy link
Member

tocsoft commented Aug 19, 2019

I feel we should also take the opportunity to remove the passing of a Rectangle into IImageProcessingContext.ApplyProcessor() & IImageProcessor.CreatePixelSpecificProcessor() the rectangle feels like it should be a concern of individual processors configuration as not all processors honor the rectangle so it becomes a bit of a lie to have it always passed.

@JimBobSquarePants
Copy link
Member

@tocsoft Yeah, I had a good dig through the code there and I don't think there's any reason why we cannot do that.

@JimBobSquarePants
Copy link
Member

Been looking at the open PR regarding this and we need to fix up the way we use the term Clone and DeepClone in the library. Currently we use both for what is a deep clone. I suggest we always use DeepClone everywhere

@antonfirsov
Copy link
Member Author

@tocsoft good idea. We need to provide specific guidance for #983 to make it happen. I'll also have a dig through the code to see what we can do.

@antonfirsov
Copy link
Member Author

antonfirsov commented Aug 24, 2019

@tocsoft @JimBobSquarePants I had a deeper look at the source, now I'm unsure whether this is the right thing to do.

In my understanding the majority of the processors actually do honor SourceRectangle. Drawing processors are an exception, but it would probably make sense to respect sourceRectangle in drawing. (Eg: user wants to draw to a given subarea of an image masking out the rest, even if some drawing artifacts go outside the bounds.)

I think we could even generalize this logic, and introduce ImageArea and ImageFrameArea<T> types:

class ImageFrameArea<T>
{
    public ImageFrame<T> Frame { get; }
    public Rectangle Rectangle { get; }
    public Span<T> GetPixelRowSpan(int y); // gets the subspan inside the area.
                                           // y = 0 is at row Rectangle.Top
    // etc.
}

Am I missing an important point? Are there other exceptions?

@antonfirsov
Copy link
Member Author

Added an update to the issue description.

@JimBobSquarePants
Copy link
Member

@antonfirsov having had a good dig through via refactoring I tend to agree. We can handle that once I finish implementing IDisposable

@JimBobSquarePants
Copy link
Member

Just pushed a PR to clean up the way we were implementing IDisposable. I'm gonna have a look at moving the visitor extensions now.

@JimBobSquarePants
Copy link
Member

I've started working on exposing the pixel blenders. The lack of method documentation is a chore though!

@antonfirsov
Copy link
Member Author

Great news! I will be available for reviews in the next days.

@JimBobSquarePants
Copy link
Member

These APIs are basically the last two methods I think we should expose. They're not currently used in Drawing but I think would be very useful elsewhere.

I'm definitely going to rename anything **Helper to **Utils for the sake of my sanity anyway.

internal static void IterateRowsWithTempBuffer<T>(

@JimBobSquarePants
Copy link
Member

I think we can close this since ImageSharp.Drawing is now in an external repository with no internal access.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants