Skip to content

Add ANI decoder support - #2899

Merged
JimBobSquarePants merged 24 commits into
SixLabors:mainfrom
zxbmmmmmmmmm:feat/ani
Jul 27, 2026
Merged

Add ANI decoder support#2899
JimBobSquarePants merged 24 commits into
SixLabors:mainfrom
zxbmmmmmmmmm:feat/ani

Conversation

@Poker-sang

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

Copy link
Copy Markdown
Contributor

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

CLAassistant commented Mar 11, 2025

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@CLAassistant

Copy link
Copy Markdown

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.

Comment thread src/ImageSharp/Formats/Ani/AniDecoderCore.cs Outdated
Comment thread src/ImageSharp/Formats/Ani/AniHeader.cs Outdated
Poker-sang and others added 2 commits March 12, 2025 20:25
Co-authored-by: Günther Foidl <gue@korporal.at>
@JimBobSquarePants

Copy link
Copy Markdown
Member

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

@JimBobSquarePants JimBobSquarePants left a comment

Copy link
Copy Markdown
Member

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.

Comment thread src/ImageSharp/Formats/Webp/RiffOrListChunkHeader.cs Outdated
Comment thread src/ImageSharp/Formats/Ani/AniDecoder.cs Outdated
Comment thread src/ImageSharp/Formats/Ani/AniMetadata.cs Outdated
Comment thread src/ImageSharp/Formats/Ani/AniMetadata.cs Outdated
Comment thread src/ImageSharp/Formats/Ani/AniDecoderCore.cs Outdated
Comment thread src/ImageSharp/Formats/Ani/AniFrameMetadata.cs Outdated
Comment thread src/ImageSharp/Formats/Ani/AniFrameMetadata.cs Outdated
Comment thread src/ImageSharp/Formats/Ani/AniFrameMetadata.cs Outdated
Comment thread src/ImageSharp/Formats/Ani/AniDecoderCore.cs Outdated
Comment thread src/ImageSharp/Formats/Ani/AniDecoderCore.cs Outdated
@Poker-sang

Copy link
Copy Markdown
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
Copy Markdown
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

Poker-sang commented Apr 8, 2025

Copy link
Copy Markdown
Contributor Author

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

@JimBobSquarePants

Copy link
Copy Markdown
Member

@Poker-sang I'll pull this down and have another deep look tonight.

@Poker-sang

Copy link
Copy Markdown
Contributor Author

New structure diagram for your reference

drawio

@JimBobSquarePants

Copy link
Copy Markdown
Member

@Poker-sang Haven't forgotten this, just focusing on closing some issues first.

@wheremyfoodat

wheremyfoodat commented Aug 30, 2025

Copy link
Copy Markdown

Would like to thank you and say that this PR is super useful to me as I'm currently working on some MacOS mouse cursor software. A lot of cursors online are made for Windows and come in .ani format, so having to support it is unavoidable and I'm very excited to try this out in the future.

@JimBobSquarePants

Copy link
Copy Markdown
Member

@Poker-sang Are you still looking to complete this?

I've just merged the latest main into your branch and added some comments, but the code still needs some work to clean it up. We'll also need a full encoder implementation.

@Poker-sang

Copy link
Copy Markdown
Contributor Author

New structure diagram for your reference

drawio

The ANI format encoder should first determine the structure of ANI files within ImageSharp (as shown in the structure diagram I shared earlier). but now some Metadata are still not included yet (CUR, ICO, BMP ImageMetadata). And I'm not sure if the current structure is ok. So perhaps we should first discuss how to design the APIs for these Metadatas?

ANI encoders are simply combinations of existing encoders which are easy to write. Don't worry about that(

# Conflicts:
#	src/ImageSharp/Formats/_Generated/ImageMetadataExtensions.cs
#	tests/ImageSharp.Tests/TestImages.cs
Copilot AI review requested due to automatic review settings July 27, 2026 02:38

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This pull request adds full ANI (Windows animated cursor) format support to ImageSharp, including decoder/encoder, metadata modeling, format detection, and default configuration registration. It also refactors existing ICO/CUR handling to better isolate embedded frame payloads and reduces allocations in several format implementations using inline buffers.

Changes:

  • Add ANI format implementation (decoder, encoder, metadata, detector, configuration module) and associated tests + test assets.
  • Refactor ICO/CUR container decoding/encoding to hard-bound embedded frame reads and improve metadata handling.
  • Replace several fixed/temporary buffers with InlineArray* storage and reuse workspace buffers in WebP/Zlib/PNG/JPEG/EXR paths.

Reviewed changes

Copilot reviewed 79 out of 79 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
tests/ImageSharp.Tests/TestImages.cs Adds ANI test image paths
tests/ImageSharp.Tests/Formats/ImageFormatManagerTests.cs Asserts ANI encoder/decoder autoloading
tests/ImageSharp.Tests/Formats/Icon/Ico/IcoDecoderTests.cs Adds detector/integrity/metadata tests for ICO
tests/ImageSharp.Tests/Formats/Icon/Cur/CurEncoderTests.cs Adds CUR encoder quantizer regression test
tests/ImageSharp.Tests/Formats/Icon/Cur/CurDecoderTests.cs Adds CUR detector/name/metadata clone tests
tests/ImageSharp.Tests/Formats/Ani/AniMetadataTests.cs Adds ANI metadata resize behavior test
tests/ImageSharp.Tests/Formats/Ani/AniEncoderTests.cs Adds ANI encoding roundtrip + metadata tests
tests/ImageSharp.Tests/Formats/Ani/AniDecoderTests.cs Adds ANI decoding/identify/integrity tests
tests/ImageSharp.Tests/ConfigurationTests.cs Updates expected default format count
tests/Images/Input/Ani/Work.ani Adds ANI test asset (LFS)
tests/Images/Input/Ani/Help.ani Adds ANI test asset (LFS)
tests/Images/Input/Ani/aero_busy.ani Adds ANI test asset (LFS)
tests/Directory.Build.targets Pins Magick.NET version for test behavior stability
src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs Reuses span-based UV workspace buffer
src/ImageSharp/Formats/Webp/Lossy/WebpLossyDecoder.cs Switches to decoder-owned upsampling buffer
src/ImageSharp/Formats/Webp/Lossy/Vp8Matrix.cs Replaces unsafe fixed buffers with inline arrays
src/ImageSharp/Formats/Webp/Lossy/Vp8Decoder.cs Allocates/reuses upsampling workspace via allocator
src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs Normalizes scratch span usage
src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs Replaces fixed scratch buffer with inline array
src/ImageSharp/Formats/Png/PngEncoderCore.cs Replaces scratch buffer with inline array
src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs Replaces unsafe fixed buffers with inline arrays
src/ImageSharp/Formats/Jpeg/Components/Decoder/ArithmeticScanDecoder.cs Replaces fixed array state with inline array
src/ImageSharp/Formats/Icon/IconImageFormatDetector.cs Removes shared ICO/CUR detector
src/ImageSharp/Formats/Icon/IconFrameStream.cs Adds bounded stream for embedded icon frames
src/ImageSharp/Formats/Icon/IconFrameCompression.cs Improves enum documentation
src/ImageSharp/Formats/Icon/IconFileType.cs Improves enum documentation/formatting
src/ImageSharp/Formats/Icon/IconEncoderCore.cs Refactors container encoding + per-frame metadata provider
src/ImageSharp/Formats/Icon/IconDirEntry.cs Adds docs and adjusts parse/write signatures
src/ImageSharp/Formats/Icon/IconDir.cs Adds docs and replaces primary ctor with explicit fields
src/ImageSharp/Formats/Icon/IconDecoderCore.cs Adds bounded decoding/identify and tighter validation
src/ImageSharp/Formats/Ico/IcoMetadata.cs Improves ICO metadata documentation
src/ImageSharp/Formats/Ico/IcoImageFormatDetector.cs Adds ICO-specific format detector
src/ImageSharp/Formats/Ico/IcoFrameMetadata.cs Fixes scaling semantics for encoded 0==256 dimensions
src/ImageSharp/Formats/Ico/IcoFormat.cs Updates default mime type constant
src/ImageSharp/Formats/Ico/IcoEncoderCore.cs Adds ICO metadata provider for shared icon encoder
src/ImageSharp/Formats/Ico/IcoEncoder.cs Simplifies encoder core invocation
src/ImageSharp/Formats/Ico/IcoDecoderCore.cs Wires icon decoder core for ICO
src/ImageSharp/Formats/Ico/IcoDecoder.cs Documentation/formatting tweaks
src/ImageSharp/Formats/Ico/IcoConstants.cs Cleans up constants/docs
src/ImageSharp/Formats/Ico/IcoConfigurationModule.cs Registers ICO detector + removes old shared detector usage
src/ImageSharp/Formats/Gif/GifDecoderCore.cs Replaces scratch buffer with inline array
src/ImageSharp/Formats/Exr/ExrEncoderCore.cs Replaces reusable buffer with inline array
src/ImageSharp/Formats/Exr/ExrDecoderCore.cs Replaces reusable buffer with inline array
src/ImageSharp/Formats/Exr/Compression/Decompressors/B44ExrCompression.cs Replaces scratch buffers with inline arrays
src/ImageSharp/Formats/Cur/CurMetadata.cs Improves CUR metadata documentation
src/ImageSharp/Formats/Cur/CurImageFormatDetector.cs Adds CUR-specific format detector
src/ImageSharp/Formats/Cur/CurFrameMetadata.cs Fixes clone + scaling semantics for 0==256 dimensions
src/ImageSharp/Formats/Cur/CurFormat.cs Fixes CUR format name + default mime type constant
src/ImageSharp/Formats/Cur/CurEncoderCore.cs Adds CUR metadata provider for shared icon encoder
src/ImageSharp/Formats/Cur/CurEncoder.cs Simplifies encoder core invocation
src/ImageSharp/Formats/Cur/CurDecoderCore.cs Wires icon decoder core for CUR
src/ImageSharp/Formats/Cur/CurDecoder.cs Documentation/formatting tweaks
src/ImageSharp/Formats/Cur/CurConstants.cs Cleans up constants/docs
src/ImageSharp/Formats/Cur/CurConfigurationModule.cs Registers CUR detector + removes old shared detector usage
src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs Adds internal frame+metadata encode overload for nesting
src/ImageSharp/Formats/Ani/AniRiffChunkHeader.cs Adds RIFF chunk header model
src/ImageSharp/Formats/Ani/AniMetadata.cs Adds ANI image-level metadata
src/ImageSharp/Formats/Ani/AniImageFormatDetector.cs Adds ANI header detector
src/ImageSharp/Formats/Ani/AniHeaderFlags.cs Adds ANI header flag enum
src/ImageSharp/Formats/Ani/AniHeader.cs Adds ANI header parsing/writing
src/ImageSharp/Formats/Ani/AniFrameStream.cs Adds bounded stream for embedded ANI frames
src/ImageSharp/Formats/Ani/AniFrameMetadata.cs Adds ANI frame metadata model + transform behavior
src/ImageSharp/Formats/Ani/AniFrameFormat.cs Adds enum for embedded frame format
src/ImageSharp/Formats/Ani/AniFormat.cs Registers ANI format metadata factories
src/ImageSharp/Formats/Ani/AniEncoderCore.cs Implements RIFF/ANI encoding (incl. nested ICO/CUR/BMP)
src/ImageSharp/Formats/Ani/AniEncoder.cs Adds public ANI encoder
src/ImageSharp/Formats/Ani/AniDecoderCore.cs Implements RIFF/ANI decode + identify + integrity policies
src/ImageSharp/Formats/Ani/AniDecoder.cs Adds public ANI decoder
src/ImageSharp/Formats/Ani/AniConstants.cs Adds ANI constants and FourCCs
src/ImageSharp/Formats/Ani/AniConfigurationModule.cs Registers ANI encoder/decoder/detector
src/ImageSharp/Formats/Ani/AniChunkType.cs Adds chunk/list/info enums for FourCC routing
src/ImageSharp/Formats/_Generated/ImageMetadataExtensions.cs Adds Ani metadata extension methods
src/ImageSharp/Formats/_Generated/ImageExtensions.Save.cs Adds SaveAsAni overloads
src/ImageSharp/Formats/_Generated/_Formats.ttinclude Adds ANI to codegen format lists
src/ImageSharp/Configuration.cs Registers AniConfigurationModule in defaults
src/ImageSharp/Compression/Zlib/ZlibInflateReader.cs Removes static buffer; uses inline stack storage
src/ImageSharp/Compression/Zlib/ChunkedReadStream.cs Adds Read(Span<byte>) path for segment reads
src/ImageSharp/Common/InlineArray.tt Expands generated inline array sizes
src/ImageSharp/Common/InlineArray.cs Adds new InlineArray* types (14/18/19/26/36/256)
Comments suppressed due to low confidence (2)

src/ImageSharp/Formats/Ani/AniEncoderCore.cs:305

  • AniEncoderCore creates nested CUR encoders but does not propagate AniEncoder.SkipMetadata, so embedded resources may still write metadata even when ANI encoding is configured to skip it.
                    this.curEncoder ??= new CurEncoderCore(new CurEncoder
                    {
                        PixelSamplingStrategy = this.encoder.PixelSamplingStrategy,
                        Quantizer = this.encoder.Quantizer,
                        TransparentColorMode = this.encoder.TransparentColorMode

src/ImageSharp/Formats/Ani/AniEncoderCore.cs:319

  • AniEncoderCore creates a nested BmpEncoder for AF_ICON-clear resources but does not propagate AniEncoder.SkipMetadata. This can result in writing color profile/metadata chunks even when the ANI encoder is configured to skip metadata.
                    BmpEncoder bmpEncoder = new()
                    {
                        BitsPerPixel = GetBmpBitsPerPixel(bitCount),
                        PixelSamplingStrategy = this.encoder.PixelSamplingStrategy,
                        Quantizer = this.encoder.Quantizer,

Comment thread src/ImageSharp/Formats/Ani/AniEncoderCore.cs
Comment thread src/ImageSharp/Formats/Icon/IconDirEntry.cs
Comment thread src/ImageSharp/Formats/Icon/IconDir.cs
Comment thread tests/ImageSharp.Tests/Formats/Ani/AniDecoderTests.cs Outdated
Comment thread src/ImageSharp/Formats/Ico/IcoConfigurationModule.cs
Copilot AI review requested due to automatic review settings July 27, 2026 03:15

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 80 out of 80 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (1)

src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs:897

  • ProcessedAlphaMask assumes the frame width is divisible by 8 (arrayWidth = Width / 8) and always reads 8 pixels per mask byte. For widths not divisible by 8 (or < 8), this drops pixels and can write an AND-mask row that is too short, producing an invalid ICO/CUR bitmap payload.
        int arrayWidth = encodingFrame.Width / 8;
        int padding = arrayWidth % 4;
        if (padding is not 0)
        {
            padding = 4 - padding;

Comment thread src/ImageSharp/Formats/Ani/AniDecoderCore.cs
Comment thread src/ImageSharp/Formats/Ani/AniDecoderCore.cs Outdated
Copilot AI review requested due to automatic review settings July 27, 2026 03:41

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 80 out of 80 changed files in this pull request and generated 2 comments.

Comment thread src/ImageSharp/Formats/Icon/IconDecoderCore.cs
Comment thread src/ImageSharp/Common/InlineArray.tt
@JimBobSquarePants

Copy link
Copy Markdown
Member

@Poker-sang I've finished this off. Thanks for your help!

@JimBobSquarePants
JimBobSquarePants merged commit 8db9663 into SixLabors:main Jul 27, 2026
11 checks passed
@Poker-sang

Copy link
Copy Markdown
Contributor Author

Thank you! 😆

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.

7 participants