Ico.Reader
2.0.0
dotnet add package Ico.Reader --version 2.0.0
NuGet\Install-Package Ico.Reader -Version 2.0.0
<PackageReference Include="Ico.Reader" Version="2.0.0" />
<PackageVersion Include="Ico.Reader" Version="2.0.0" />
<PackageReference Include="Ico.Reader" />
paket add Ico.Reader --version 2.0.0
#r "nuget: Ico.Reader, 2.0.0"
#:package Ico.Reader@2.0.0
#addin nuget:?package=Ico.Reader&version=2.0.0
#tool nuget:?package=Ico.Reader&version=2.0.0
Ico.Reader
Ico.Reader is a cross-platform library designed for extracting icons and cursors from .ico and .cur files, as well as from embedded resources within .exe and .dll files.
Installation
dotnet add package Ico.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 images from ICO, CUR, EXE, and DLL files without relying on Windows-specific functions, making it fully cross-platform.
- Supports Both Icons and Cursors: Reads both icons (.ico) and cursors (.cur) from standalone files and embedded resources within executables.
- Format Conversion: Converts BMP images to PNG format during extraction, supporting a more universally compatible image format across different platforms.
- Efficient Memory Usage: Implements a method to read icons that minimizes memory usage by delaying the loading of image data until it is needed.
- Flexible Data Access: Supports extracting ico's from file paths, byte arrays, and streams, accommodating various application scenarios.
- Selective Image Extraction: Detailed ICO information, including groupings and image references, is provided upfront.
Getting Started
Reading icoData
var icoReader = new IcoReader();
// Reading from a file path (most memory-efficient)
IcoData iconFromPath = icoReader.Read("path/to/your/icon.ico");
IcoData cursorFromPath = icoReader.Read("path/to/your/cursor.cur");
IcoData icoFromPathDll = icoReader.Read("path/to/your/user32.dll");
IcoData icoFromPathEXE = icoReader.Read("path/to/your/regedit.exe");
// Reading from a byte array
byte[] icoBytes = File.ReadAllBytes("path/to/your/icon.ico");
IcoData icoFromBytes = icoReader.Read(icoBytes);
// Reading from a stream (copies the stream for independent access)
using var stream = File.OpenRead("path/to/your/icon.ico");
IcoData icoFromStream = icoReader.Read(stream: stream, copyStream: true);
// Reading asynchronously
IcoData iconAsync = await icoReader.ReadAsync("path/to/your/icon.ico", cancellationToken);
// Reading from a stream without copying (as efficient as direct file reading)
using (var streamOrigin = File.OpenRead("path/to/your/icon.ico"))
{
IcoData icoFromStreamDirect = icoReader.Read(stream: streamOrigin, copyStream: false);
// ✅ This is as memory-efficient as reading directly from a file.
// 🔴 WARNING: All images must be accessed before closing the stream,
// otherwise an error will occur.
}
copyStream: true→ The stream is copied, allowing access to images even after the original stream is closed. The stream does not have to 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 remain open while accessing images, and reading images moves its position. Accessing an image after it closes throwsObjectDisposedException.
Every stream overload, ReadAsync included, reads from the stream's current position. A stream you have just written the data into stands at its end, so set Position = 0 before reading it.
Thread Safety
IcoReader is safe to share across threads, and so is any IcoData read from a file path, a byte array, or a copied stream — each image read opens its own stream, so concurrent calls to GetImage, GetImageAsync and the save methods are fine.
The one exception is copyStream: false. That mode reads directly from the stream you supplied, so all readers share a single position and it must be used by one thread at a time. Use copyStream: true if several threads need the same IcoData.
Note: All
Read()overloads returnnullif the file does not exist, the format is unrecognized or the data cannot be parsed.
Retrieving Image from icoData
Retrieving Images by Index
Each image within an ico file is assigned a unique index, accessible through the ImageReferences collection within icoData. You can retrieve the image data by specifying this index.
// Synchronously retrieve image data by index
byte[] imageData = icoData.GetImage(0);
// Asynchronously retrieve image data by index
byte[] imageDataAsync = await icoData.GetImageAsync(0, cancellationToken);
Retrieving Images by Group
ICO files, especially those embedded in executables (EXEs) or dynamic link libraries (DLLs), can organize images into groups.
Ico.Reader standardizes group handling by treating standalone ICO and CUR files as single-group sources, while DLLs and EXEs may contain multiple groups for icons and cursors.
Retrieving images by group involves specifying both the group object and the image index within that group. The following examples illustrate synchronous and asynchronous retrieval methods:
Retrieving Images from Standalone ICO or CUR Files
ICO and CUR files contain only one image group. To retrieve the first image in that group:
// Get the first image in the group as PNG data
var group = icoData.Groups[0];
byte[] groupImageData = icoData.GetImage(group, 0);
Retrieving Images from DLLs or EXEs
DLLs and EXEs may contain multiple image groups for both icons and cursors.
// Retrieve a cursor group (e.g., ID 105) and get the first image
var cursorGroup = icoData.GetGroup("105", IcoType.Cursor);
byte[] cursorImageData = icoData.GetImage(cursorGroup, 0);
// Retrieve an icon group (e.g., ID 32656) and get the first image
var iconGroup = icoData.GetGroup("32656", IcoType.Icon);
byte[] iconImageData = icoData.GetImage(iconGroup, 0);
Retrieving All Images Asynchronously by Groups
To iterate over all groups and retrieve all images asynchronously:
var imageDatas = new List<byte[]>();
foreach (var group in icoData.Groups)
{
for (int i = 0; i < group.DirectoryEntries.Count; i++)
{
byte[] imageData = await icoData.GetImageAsync(group, i);
imageDatas.Add(imageData);
}
}
Retrieving All Images Asynchronously by image references
To iterate over all image references and retrieve all images asynchronously:
var imageDatas = new List<byte[]>();
foreach (var imageReference in icoData.ImageReferences)
{
byte[] imageData = await icoData.GetImageAsync(imageReference);
imageDatas.Add(imageData);
}
ImageReference Properties
Each ImageReference exposes metadata about the individual image:
| Property | Type | Description |
|---|---|---|
Width |
int |
Image width in pixels |
Height |
int |
Image height in pixels |
BitCount |
int |
Bit depth (e.g. 1, 4, 8, 16, 24, 32) |
Format |
IcoImageFormat |
Bmp or Png |
IcoType |
IcoType |
Icon or Cursor |
HotspotX |
ushort |
Cursor hotspot X coordinate (cursors only) |
HotspotY |
ushort |
Cursor hotspot Y coordinate (cursors only) |
Selecting the Preferred Image Based on Quality
To select the preferred image, Ico.Reader calculates a quality score for each image from its pixel area and colour bit depth.
Each is scored as a fraction of the best value present, and the two are combined using the supplied weights.
The weights are a ratio and are normalised internally, so colorBitWeight: 1, areaWeight: 2 ranks identically to 0.333 and 0.667.
The preferred image is the one with the highest calculated quality score.
You can retrieve the preferred image either globally (from all groups) or from a specific group:
// Selecting the preferred image globally from all groups
// Default: area counts twice as much as colour depth
int preferredIndex = icoData.PreferredImageIndex();
var imageRef = icoData.ImageReferences[preferredIndex];
var imageData = icoData.GetImage(imageRef);
// Weight colour depth more heavily than size
int deepestIndex = icoData.PreferredImageIndex(colorBitWeight: 2f, areaWeight: 1f);
// Ignore colour depth entirely and take the largest image
int largestIndex = icoData.PreferredImageIndex(colorBitWeight: 0f, areaWeight: 1f);
// Selecting the preferred image from a specific group
int preferredGroupIndex = icoData.PreferredImageIndex(selectedGroup);
var groupImageRef = icoData.ImageReferences[preferredGroupIndex];
var groupImageData = icoData.GetImage(groupImageRef);
Returns -1 when there are no images to choose from.
Saving / Exporting Images
Writing images to disk is the job of IcoExporter. It has no dependencies, so a container is optional — see Dependency Injection Support if you prefer to inject IIcoExporter.
var exporter = new IcoExporter();
// Save a single image
await exporter.SaveImageAsync(icoData, icoData.ImageReferences[0], "output/image_0.png");
// Save a single image from a specific group
await exporter.SaveImageAsync(icoData, icoData.GetImageReference(group, 0), "output/group_image.png");
// Save all images in a group into a directory (one file per image)
await exporter.SaveGroupToDirectoryAsync(icoData, group, "output/group/");
// Save all groups into subdirectories of a root directory
await exporter.SaveAllGroupsToDirectoryAsync(icoData, "output/");
// Save all images flat into a single directory
await exporter.SaveAllImagesToDirectoryAsync(icoData, "output/flat/");
Every asynchronous method accepts a CancellationToken:
await exporter.SaveAllImagesToDirectoryAsync(icoData, "output/", cancellationToken);
Configuration
By default, IcoReader uses built-in decoders. You can supply a custom IcoReaderConfiguration to override the ICO or PE decoder:
var config = new IcoReaderConfiguration
{
IcoDecoder = new MyCustomIcoDecoder(),
IcoExeDecoder = new MyCustomPeDecoder()
};
var icoReader = new IcoReader(config);
IcoReaderConfiguration properties:
| Property | Type | Description |
|---|---|---|
IcoDecoder |
IIcoDecoder |
Decoder for standalone .ico / .cur files |
IcoExeDecoder |
IIcoPeDecoder |
Decoder for embedded resources in .exe / .dll files |
Dependency Injection Support
For applications utilizing Dependency Injection, Ico.Reader provides an extension method to seamlessly register its services with the DI container. This enables easy configuration and integration into your projects, ensuring that all necessary components are available for ico reading and decoding tasks.
To add Ico.Reader services to your project's service collection:
public void ConfigureServices(IServiceCollection services)
{
services.AddIcoReader();
}
This registers IcoReader, IIcoExporter and the decoders. Using a container is entirely optional: every one of those types also has a parameterless constructor, so new IcoReader() and new IcoExporter() give you the same composition.
Dependencies
'Ico.Reader' is designed with minimal external dependencies to ensure lightweight integration into your projects.
For projects utilizing 'Ico.Reader', the primary dependency to be aware of is:
| 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
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 2.1.0)
- System.Memory (>= 4.6.3)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on Ico.Reader:
| Package | Downloads |
|---|---|
|
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. |
GitHub repositories
This package is not used by any popular GitHub repositories.