Add ANI decoder support - #2899
Conversation
|
|
Co-authored-by: Günther Foidl <gue@korporal.at>
|
Thanks @Poker-sang I’ll have a deep look at this asap. |
JimBobSquarePants
left a comment
There was a problem hiding this comment.
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.
|
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:
|
I don't think we should store the rate block in the Thinking about it some more I believe a good plan would be to have an enum in We can then use nullable sub properties for |
|
I make every |
|
@Poker-sang I'll pull this down and have another deep look tonight. |
|
@Poker-sang Haven't forgotten this, just focusing on closing some issues first. |
|
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. |
|
@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. |
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 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
There was a problem hiding this comment.
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,
There was a problem hiding this comment.
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;
|
@Poker-sang I've finished this off. Thanks for your help! |
|
Thank you! 😆 |


Prerequisites
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
Metadataas 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 modificationsANI is also a RIFF file, so I referenced RiffHelper, perhaps we should move it out of the WEBP namespace
[1]
[2]