Ani.Reader
2.0.0
dotnet add package Ani.Reader --version 2.0.0
NuGet\Install-Package Ani.Reader -Version 2.0.0
<PackageReference Include="Ani.Reader" Version="2.0.0" />
<PackageVersion Include="Ani.Reader" Version="2.0.0" />
<PackageReference Include="Ani.Reader" />
paket add Ani.Reader --version 2.0.0
#r "nuget: Ani.Reader, 2.0.0"
#:package Ani.Reader@2.0.0
#addin nuget:?package=Ani.Reader&version=2.0.0
#tool nuget:?package=Ani.Reader&version=2.0.0
Ani.Reader
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
.anifiles 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
LoadsOnWindowstells 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
WebPCreatorextension.
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 returnnullif 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.anifile of that kind returnsnull, 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.
- Ico.Reader
- Magick.NET-Q16-AnyCPU (WebP export only)
- Microsoft.Extensions.DependencyInjection.Abstractions (2.1.0 or later)
| Product | Versions 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. |
-
.NETStandard 2.0
- Ico.Reader (>= 2.0.0 && < 3.0.0)
- Magick.NET-Q16-AnyCPU (>= 14.17.1)
- Microsoft.Bcl.HashCode (>= 6.0.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 2.1.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.