Skip to content

Add ANI decoder support #2899

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

Open
wants to merge 18 commits into
base: main
Choose a base branch
from
Open

Conversation

Poker-sang
Copy link
Contributor

@Poker-sang Poker-sang commented Mar 11, 2025

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 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

implements AniDecoder for ImageSharp, and encoder if the pr approved

Comment

Since the ANI file may contain a two-dimensional array of ImageFrames, I flattened the original ImageFrames as follows

I have kept all Metadata as much as possible, but this may result in a structure that is not intuitive, and may require further discussion with you) reserved the possibility of future modifications

drawio

ANI is also a RIFF file, so I referenced RiffHelper, perhaps we should move it out of the WEBP namespace

[1]
[2]

@CLAassistant
Copy link

CLAassistant commented Mar 11, 2025

CLA assistant check
All committers have signed the CLA.

@CLAassistant
Copy link

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you all sign our Contributor License Agreement before we can accept your contribution.
1 out of 2 committers have signed the CLA.

✅ Poker-sang
❌ zxbmmmmmmmmm
You have signed the CLA already but the status is still pending? Let us recheck it.

@JimBobSquarePants
Copy link
Member

Thanks @Poker-sang I’ll have a deep look at this asap.

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.

Thanks for this. It's a promising start...

I think we can make some changes here to allow flattening out the frame sequences a little better which should help when writing the encoder.

I've added some additional comments regarding coding style and merging of functionality also.


namespace SixLabors.ImageSharp.Formats.Webp;

internal readonly struct RiffOrListChunkHeader
Copy link
Member

Choose a reason for hiding this comment

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

I don't think we should be merging functionality like this under format specific namespaces. Duplication is actually better for trimming.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Should I split it into RiffChunkHeader and ListChunkHeader?

/// <summary>
/// Gets or sets the <see cref="ImageFrameMetadata"/> each "icon" chunk in ANI file.
/// </summary>
public IList<ImageFrameMetadata> IconFrames { get; set; } = [];
Copy link
Member

Choose a reason for hiding this comment

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

I don't think this is wise. We've ended up with a type that can hold and store any image format metadata.

/// <summary>
/// Gets or sets the frames count of **one** "icon" chunk.
/// </summary>
public int FrameCount { get; set; } = 1;
Copy link
Member

Choose a reason for hiding this comment

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

This should be the sequence number.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It is actually intentionally designed as FrameCount, because each frame of ani file may contain multiple subframes (similar to different resolution ICOs), so this field is used to indicate how many subframes are contained in this frame

Copy link
Member

Choose a reason for hiding this comment

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

Yes. The way I'm thinking is to use a SequenceNumber property to identify which individual frames of different resolutions belong to a single ANI frame. Imagine the individual frames following this sequence:
1,1,1,2,3,4,4,4.

When we write the encoder (we'll need one to complete this PR) we can then use those sequence numbers to group the frames for encoding.


List<ImageFrame<TPixel>> list = [];

foreach (int i in sequence)
Copy link
Member

Choose a reason for hiding this comment

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

We need to flatten out the frames and metadata here. Each frame should have an AniFrameMetadata property containing all the required information including what sequence the frame belongs to.

No other format metadata should be added to the output.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

How can i remove the FrameMetadata from decoded frame? Can I know if a Metadata is included without creating a new Metadata?

@Poker-sang
Copy link
Contributor Author

Thank you for the review ❤ I fixed some simple review comments, but there is a lot of work that needs to continue to be discussed before we can move forward.

Based on your suggestions I'm guessing it may need to be improved in this way:

  1. The rate block section could probably be included into AniMetadata

  2. Include all members of Ico/Cur/BmpMetadata in AniFrameMetadata and indicate which fields to use via flag. bmp is simpler, but for Ico/Cur frames could be specified using SequenceNumber for encode.

@JimBobSquarePants
Copy link
Member

Thank you for the review ❤ I fixed some simple review comments, but there is a lot of work that needs to continue to be discussed before we can move forward.

Based on your suggestions I'm guessing it may need to be improved in this way:

  1. The rate block section could probably be included into AniMetadata
  2. Include all members of Ico/Cur/BmpMetadata in AniFrameMetadata and indicate which fields to use via flag. bmp is simpler, but for Ico/Cur frames could be specified using SequenceNumber for encode.

I don't think we should store the rate block in the AniMetadata as we can have much more granular control when stored as FrameDelay in individual AniFrameMetadata instances. When encoding we can check if there is any variation in those values and use the header for all rates; otherise individual rate chunks.

Thinking about it some more I believe a good plan would be to have an enum in AniFrameMetadata of type AniFrameFormat which states whether the frame is Ico/Cur/Bmp.

We can then use nullable sub properties for IcoFrameMetadata, and CurFrameMetadata to AniFrameMetadata. which will save some effort. We can add NotNullWhen attributes to the properties based on the value of our AniFrameFormat property.

@Poker-sang
Copy link
Contributor Author

Poker-sang commented Apr 8, 2025

I make every ImageFrame has its own AniFrameMetadata, and where should we place Bmp/Ico/CurMetadata (not FrameMetadata)?

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.

5 participants