Ani.Reader 2.0.0

dotnet add package Ani.Reader --version 2.0.0
                    
NuGet\Install-Package Ani.Reader -Version 2.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Ani.Reader" Version="2.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Ani.Reader" Version="2.0.0" />
                    
Directory.Packages.props
<PackageReference Include="Ani.Reader" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Ani.Reader --version 2.0.0
                    
#r "nuget: Ani.Reader, 2.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Ani.Reader@2.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Ani.Reader&version=2.0.0
                    
Install as a Cake Addin
#tool nuget:?package=Ani.Reader&version=2.0.0
                    
Install as a Cake Tool

Ani.Reader icon Ani.Reader

NuGet Version NuGet Downloads CI

Ani.Reader is a cross-platform library designed for extracting animated cursors from .ani files, as well as from embedded resources within .exe and .dll files.

Installation

dotnet add package Ani.Reader

Requirements: .NET Standard 2.0 or later (compatible with .NET Framework 4.6.1+, .NET Core 2.0+, .NET 5+).

Key Features

  • Platform-Independent Design: Extracts animated cursors from ANI, EXE, and DLL files without relying on Windows-specific functions, making it fully cross-platform.
  • Supports ANI Files and PE Resources: Reads animated cursors from standalone .ani files as well as embedded resources within executables and DLLs.
  • Efficient Memory Usage: Implements lazy frame loading — individual frame image data is not decoded until it is needed.
  • Multiple Animation Variants: A single ANI source can contain variants at different sizes and bit depths, each accessible as a separate AnimationInformation.
  • Windows-Exact Playback: Animations Windows loads play exactly the steps Windows plays, and LoadsOnWindows tells whether Windows accepts an animation at all.
  • Flexible Data Access: Supports reading from file paths, byte arrays, and streams.
  • WebP Export: Animated cursors can be exported as animated WebP images via the built-in WebPCreator extension.

Getting Started

Reading aniData

var aniReader = new AniReader();

// Reading from a file path (most memory-efficient)
AniData[]? aniFromPath = aniReader.Read("path/to/cursor.ani");
AniData[]? aniFromDll  = aniReader.Read("path/to/user32.dll");
AniData[]? aniFromExe  = aniReader.Read("path/to/app.exe");

// Reading from a byte array
byte[] aniBytes = File.ReadAllBytes("path/to/cursor.ani");
AniData[]? aniFromBytes = aniReader.Read(aniBytes);

// Reading from a stream (copies the stream for independent access)
using var stream = File.OpenRead("path/to/cursor.ani");
AniData[]? aniFromStream = aniReader.Read(stream: stream, copyStream: true);

// Reading from a stream without copying (as efficient as direct file reading)
using (var streamOrigin = File.OpenRead("path/to/cursor.ani"))
{
    AniData[]? aniFromStreamDirect = aniReader.Read(stream: streamOrigin, copyStream: false);
    // ✅ This is as memory-efficient as reading directly from a file.
    // 🔴 WARNING: All frames must be accessed before closing the stream,
    // otherwise an error will occur.
}
  • copyStream: true → The stream is copied, allowing access to frames even after the original stream is closed. The stream need not be seekable.
  • copyStream: false → The stream is used directly, making it as memory-efficient as reading from a file, but the stream must be seekable and must remain open while accessing frames.

Both modes read from the stream's current position, so an animation that follows other data in a stream can be read by positioning the stream at its start first.

Note: All Read() overloads return null if the file does not exist, the format is unrecognised, or the data cannot be parsed. Animations whose frames are raw bitmaps rather than icons or cursors, and animations in which no frame holds an image, are left out: an .ani file of that kind returns null, and an executable or DLL returns the animations that remain, or an empty array when it holds none.

Working with AniData

Read() returns an AniData[] because a single PE file can embed multiple animated cursors. Each AniData represents one animation and exposes its frames and animation variants.

AniData Properties
Property Type Description
Name string Derived from the file name or PE resource ID
Origin AniOriginFileType Executable, Dll, or Ani
TotalAnimationDuration TimeSpan Sum of all step durations
TotalFrames int Number of steps in Frames
FrameRate float Frames per second (60 / DisplayRate)
Frames ReadOnlyCollection<FrameInformation> Ordered sequence of frame steps including timing
Animations ReadOnlyCollection<AnimationInformation> Sizes the animation can be played back at
LoadsOnWindows bool Whether Windows loads this animation

An animation Windows loads plays exactly the steps Windows plays. One Windows refuses, such as a file whose seq chunk does not match its header, is still read as far as it can be, and LoadsOnWindows is false. The XML documentation of LoadsOnWindows lists the rules.

FrameInformation Properties
Property Type Description
Position int Index of this step in the animation sequence
Start TimeSpan Time offset when this step begins
Duration TimeSpan How long this step is displayed
VariationDetails IEnumerable<FrameVariationInformation> Every image the step's frame holds, with size and hotspot
FrameReference AniFrameReference Raw byte offset and size within the source stream
AnimationInformation Properties
Property Type Description
Width int Frame width in pixels
Height int Frame height in pixels
BitCount int Bit depth, counting PNG images as 32-bit
FrameHotspots IEnumerable<FrameHotspot> Cursor hotspot position per frame step

Animations holds one entry per size that every frame with an image has. When the frames share no size, it holds the sizes of the first frame with an image instead, and each other frame gives the image closest in size.

Retrieving Frame Data

Each AnimationInformation describes one size/depth variant. Use it to retrieve the decoded PNG bytes of a specific frame:

foreach (var aniData in aniDatas)
{
    var animation = aniData.Animations[0];

    // Get the decoded PNG bytes for a single frame, or null when the step's frame holds no readable image
    byte[]? frameBytes = await aniData.GetFrameBytes(animation, aniData.Frames[0]);
}

Selecting the Preferred Animation Variant

AniData.PreferredAnimationIndex() returns the index of the AnimationInformation with the highest quality score. Each variant scores its pixel area and its bit depth, both relative to the largest in the animation, weighted by areaWeight (2 by default) and colorBitWeight (1 by default). The weights work as for Ico.Reader's PreferredImageIndex.

int preferredIndex = aniData.PreferredAnimationIndex();
var animation = aniData.Animations[preferredIndex];

// Favor color depth over size
int deepestIndex = aniData.PreferredAnimationIndex(colorBitWeight: 2f, areaWeight: 1f);

Saving / Exporting

Saving Frames as PNG
// Save all frames of an animation variant as individual PNG files
await aniData.SaveImages("output/", animation);
Exporting as Animated WebP
// Save as an animated WebP file
await aniData.SaveAsWebP("output/cursor.webp", animation);

// Get the animated WebP as a byte array
byte[] webpBytes = await aniData.GetWebpBytes(animation);

Steps shown for an hour or longer are left out of the WebP. When that leaves no step, the first frame with an image is written as a still image.

Configuration

By default, AniReader uses built-in decoders. You can supply a custom AniReaderConfiguration to override any part of the decoding pipeline:

var config = new AniReaderConfiguration
{
    AniDecoder   = new MyCustomAniDecoder(),
    AniPeDecoder = new MyCustomAniPeDecoder(),
    IcoReader    = new IcoReader()
};

var aniReader = new AniReader(config);

AniReaderConfiguration properties:

Property Type Description
AniDecoder IAniDecoder Decoder for parsing ANI RIFF data
AniPeDecoder IAniPeDecoder Decoder for extracting ANI resources from .exe / .dll files
IcoReader IcoReader ICO reader used to decode individual animation frames
IcoExporter IIcoExporter ICO exporter used to write decoded frames to disk

Dependency Injection Support

For applications utilizing Dependency Injection, Ani.Reader provides an extension method to seamlessly register its services with the DI container.

public void ConfigureServices(IServiceCollection services)
{
    services.AddAniReader();
}

Dependencies

Ani.Reader is designed with minimal external dependencies to ensure lightweight integration into your projects.

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
2.0.0 104 9/14/2026
1.2.7 225 5/23/2026
1.2.6 122 5/20/2026