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

API Image frames refactor #326

Merged
merged 28 commits into from
Sep 12, 2017
Merged

API Image frames refactor #326

merged 28 commits into from
Sep 12, 2017

Conversation

tocsoft
Copy link
Member

@tocsoft tocsoft commented Sep 9, 2017

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 PR has a few API changed and a significate internal refactor of where pixel data is stored.

  1. refactors the internals of where image data is stored such that all pixel data is now help within ImageFrames, every Image will always have at least one image frame (unless its been disposed).
  2. hides accesses Span apis into the SixLabor.ImageSharp.Advanced namespace as these APIs are "Use at your own risk".
  3. Adds the missing SavePixelData() apis to pair with the LoadPixelData apis to access the raw byte array holding the image pixels data.

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.

Looks good to me.

Quick question. Do you think we should seal our Image and ImageFrame classes? I'm thinking of people attempting to build things like integral images.

https://github.com/andrewkirillov/AForge.NET/blob/master/Sources/Imaging/IntegralImage.cs

They could inherit our image class and utilize the available API.

@tocsoft
Copy link
Member Author

tocsoft commented Sep 10, 2017

I think it would be almost impossible at the moment to make that work for the current version... we would have to make a heap more of the internals public.

I feel we should leave it sealed for v1, we can always relax things as people hit the limits of our current implementation, I feel moving over to an interface based version of the API would end up being the better choice than just unsealing Image.

@JimBobSquarePants
Copy link
Member

@tocsoft I can live with that for now.

@antonfirsov
Copy link
Member

@JimBobSquarePants I think we should keep our stuff sealed. People should follow practices like composition over inheritance for these cases like integral images.

@JimBobSquarePants
Copy link
Member

Just noticed this issue, can we handle this better now?

#284

@tocsoft
Copy link
Member Author

tocsoft commented Sep 11, 2017

sort of we would still need and Image<T> to encode from so we could create a temp Image with just the one frame (which due to the fact we store all data in frames now be a relatively cheep operation) and then just encode that... would need to think about the best way though... would probably be better to have configuration on the encoders to let use specify which FrameIndex to treat as the source of data to encode (excluding gif of course)

/// <summary>
/// Gets the root frame.
/// </summary>
public ImageFrame<TPixel> RootFrame => this.frames.Count > 0 ? this.frames[0] : null;
Copy link
Member

Choose a reason for hiding this comment

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

Is it valid to have 0 frames? Shouldn't we throw in the constructor instead?

Copy link
Member Author

Choose a reason for hiding this comment

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

yes you can have zero frames, it will have zero when the ImageFrameCollection as been disposed.

Copy link
Member

@antonfirsov antonfirsov Sep 11, 2017

Choose a reason for hiding this comment

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

Q1: Shouldn't we throw ObjectDisposedException instead of returning null then?
Q2: Looks like it's possible to dispose an ImageFrameCollection without disposing the underlying image bringing Image<T> into an invalid state. Shouldn't we drop IDisposable from ImageFrameCollection<T> API and dispose frames directly from Image<T>.Dispose()?

Copy link
Member Author

Choose a reason for hiding this comment

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

  1. yes we should probably throw an ObjectDisposedException.

  2. I don;t think its really an issue that you can dispose of the ImageFrameCollection without disposing of the image. You can do the same for ImageFrame too. I suppose we could have the Image expose and interface of IImageFrameCollection that doesn't support IDisposeable then the users can't call dispose of the collection separately to the Image.

/// </summary>
/// <param name="index"> The zero-based index at which item should be inserted..</param>
/// <param name="frame">The <seealso cref="ImageFrame{TPixel}"/> to insert into the <seealso cref="Image{TPixel}"/>.</param>
public void Insert(int index, ImageFrame<TPixel> frame)
Copy link
Member

Choose a reason for hiding this comment

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

Is it really a good idea to expose a mutable List API on ImageFrameCollection? Feels like the API surface is to wide now.
I'd rather keep it as an immuatble collection. If someone wants to implement a gif frame manipulator tool with ImageSharp (looks like a very uncommon use case), he should probably construct new image instances after calculating the frames.

Copy link
Member Author

Choose a reason for hiding this comment

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

if its immutable then you can't create an animated gif.. also the public api doesn't allow users to specify frames so the only way for a user to go from zero to animated gif is to create a series of images, merge their frames together into a single image instance and then save that.

Copy link
Member

Choose a reason for hiding this comment

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

It's probably matter of API taste, but I would utilize builder pattern instead, having some kind of ImageBuilder object to construct multi-frame images. This looks like a more convenient way to preserve valid object state. Constructing ImageFrame instances of a certain size, and verifying it against the image dimensions when calling ImageFrameCollection.Add() looks a bit clanky to me.

But looks like ImageSharp API follows mutable style on many other places (like ImageMetaData) so maybe it's easier to conform to this style everywhere for 1.0.

How about encapsulating the consistance rules into ImageFrameCollection by hiding ImageFrame<T> constructors and define methods on ImageFrameCollection<T>:

public ImageFrame<T> AppendNewFrame() { }
public ImageFrame<T> InsertNewFrame(int index) { }

?

@JimBobSquarePants thoughts?

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 had thought about adding those as APIs (well in concept not name, couldn't think of what to call them) but I like it.

This makes me think we should find a way to extend the Mutate() apis to allow us to apply mutations to single frames... might need to extend expand IImageProcessors, as we will need IFrameProcessors as well as IImageProcessors will also have to scope some of the APIs to allow targeting only images and not frames (resize, crop, rotate etc) unless we allow images to have frames with multiple dimensions and handle normalising frame sizes on a per encoder bases instead of during add frame (i.e. update gif to draw based on the first frame dimensions)

Copy link
Member

Choose a reason for hiding this comment

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

@antonfirsov I see where you're going there and it does seem safer. As far as I can see we don't actually new up a frame anywhere within our codebase.

@tocsoft I'd rather we treated images as a whole and avoid adding IFrameProcessor's. Seems like we'd be creating a complexity level that would be difficult to follow.

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 think we should leave it as is for now then... we can worry about that later if/when people hit issues with the API.

@antonfirsov
Copy link
Member

Everything LGTM except the mutable ImageFrameCollection API. It's much easier to maintain a consistent Image<T> state if we close down that list.

@tocsoft
Copy link
Member Author

tocsoft commented Sep 12, 2017

@SixLabors/core so we happy enough now with this to merge into beta?

@antonfirsov
Copy link
Member

We are really short on time, so OK for now.
In long term I'm planning to suggest deep changes to the frame API, because I'm not happy with the "greatest common divisor" approach we used to pick the gif model as an universal model for Image<TPixel>.
Hope we can go for an early 2.0 ...

@tocsoft
Copy link
Member Author

tocsoft commented Sep 12, 2017

ok merging this.. we can continue any final tweeks on the beta branch

@tocsoft tocsoft merged commit 6f4144c into beta-1 Sep 12, 2017
@tocsoft tocsoft deleted the image-frames branch September 14, 2017 18:22
antonfirsov pushed a commit to antonfirsov/ImageSharp that referenced this pull request Nov 11, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants