Lusamine.ImageIdentifier
1.3.0
dotnet add package Lusamine.ImageIdentifier --version 1.3.0
NuGet\Install-Package Lusamine.ImageIdentifier -Version 1.3.0
<PackageReference Include="Lusamine.ImageIdentifier" Version="1.3.0" />
<PackageVersion Include="Lusamine.ImageIdentifier" Version="1.3.0" />
<PackageReference Include="Lusamine.ImageIdentifier" />
paket add Lusamine.ImageIdentifier --version 1.3.0
#r "nuget: Lusamine.ImageIdentifier, 1.3.0"
#:package Lusamine.ImageIdentifier@1.3.0
#addin nuget:?package=Lusamine.ImageIdentifier&version=1.3.0
#tool nuget:?package=Lusamine.ImageIdentifier&version=1.3.0
Lusamine.ImageIdentifier
Identify an image's format and pixel dimensions by reading only its header. The full image is never loaded into memory. Works over any readable stream, including non-seekable ones (network, compression).
Supported formats: PNG, JPEG, GIF, BMP, WebP (VP8/VP8L/VP8X), TIFF, ICO.
Usage
using Lusamine.ImageIdentifier;
var identifier = new ImageIdentifier();
using var stream = File.OpenRead("photo.jpg");
ImageInfo? info = identifier.Identify(stream);
if (info is not null)
Console.WriteLine($"{info.Format} {info.Width}x{info.Height}");
Identify returns null when the data is unrecognized or the header is truncated.
Why
Only the leading header bytes are read (typically well under 1 KB), so identifying a multi-megabyte image is allocation-light and near-instant. For non-seekable streams the reader skips forward by reading and discarding, so it still never buffers the whole file.
A few formats keep their dimensions past the first header bytes; these are reached by skipping forward, never by loading the file:
- JPEG — the
SOFnmarker is located by walking the marker chain, scanning up to 2 MB to clear largeAPPnmetadata (a max-size EXIF block plus stacked ICC-profile segments). - TIFF / BigTIFF — both the classic (magic 42, 32-bit offsets) and BigTIFF (magic 43, 64-bit offsets) variants are supported. The first IFD is read wherever it sits, up to a 128 MB offset cap. Backward IFD offsets are rejected since the reader is forward-only.
- SVG —
width/heightare read first (bare orpxvalues), falling back toviewBox. Relative units (%,em, …) can't be resolved to pixels, so a file with only relative sizes and noviewBoxreturnsnull.
These caps bound work on crafted input; ordinary files resolve within the first read.
Performance
Benchmarks run against ImageSharp's Image.Identify on .NET 10, Windows 11, Intel Core i7-11800H:
| Library | Format | Mean | Ratio | Allocated |
|---|---|---|---|---|
| Lusamine | bmp | 58.71 ns | 1.00 | 256 B |
| ImageSharp | bmp | 618.13 ns | 10.57 | 1,144 B |
| Lusamine | gif | 39.53 ns | 1.00 | 224 B |
| ImageSharp | gif | 25,894,502.47 ns | 658,221.37 | 6,224 B |
| Lusamine | jpeg | 116.84 ns | 1.00 | 256 B |
| ImageSharp | jpeg | 1,883.00 ns | 16.13 | 10,768 B |
| Lusamine | png | 42.09 ns | 1.00 | 256 B |
| ImageSharp | png | 5,390.30 ns | 128.68 | 1,520 B |
| Lusamine | webp | 64.48 ns | 1.00 | 256 B |
| ImageSharp | webp | 1,584.73 ns | 24.66 | 1,424 B |
| Lusamine | tiff | 131.42 ns | 1.00 | 256 B |
| ImageSharp | tiff | 62,062.19 ns | 472.65 | 220,437 B |
And on .NET 10, macOS Tahoe 26.4, Apple M3:
| Library | Format | Mean | Ratio | Allocated |
|---|---|---|---|---|
| Lusamine | bmp | 35.88 ns | 1.00 | 256 B |
| ImageSharp | bmp | 459.39 ns | 12.81 | 1,144 B |
| Lusamine | gif | 26.55 ns | 1.00 | 224 B |
| ImageSharp | gif | 41,702,047.39 ns | 1,571,280.33 | 6,224 B |
| Lusamine | jpeg | 64.35 ns | 1.00 | 224 B |
| ImageSharp | jpeg | 1,026.48 ns | 15.96 | 10,768 B |
| Lusamine | png | 26.64 ns | 1.00 | 256 B |
| ImageSharp | png | 7,844.73 ns | 294.64 | 1,520 B |
| Lusamine | webp | 39.98 ns | 1.00 | 256 B |
| ImageSharp | webp | 1,030.49 ns | 25.78 | 1,424 B |
| Lusamine | tiff | 78.10 ns | 1.00 | 256 B |
| ImageSharp | tiff | 36,514.15 ns | 467.55 | 220,440 B |
Lusamine reads only the image header (typically under 1 KB), while ImageSharp decodes significantly more of the file. The result is consistently one to several orders of magnitude faster across formats, from ~11–13x for BMP up to hundreds of thousands of times faster for GIF, with a fraction of the allocations.
The advantage is just as large on the failure paths — unrecognized, empty, or corrupt input. Lusamine returns null cheaply and never allocates an exception, whereas ImageSharp throws and pays for it (.NET 10, macOS Tahoe 26.4, Apple M3):
| Library | Scenario | Mean | Ratio | Allocated |
|---|---|---|---|---|
| Lusamine | corrupt-jpeg | 85.79 ns | 1.00 | 192 B |
| ImageSharp | corrupt-jpeg | 6,180.97 ns | 72.05 | 10,696 B |
| Lusamine | empty | 37.55 ns | 1.00 | 152 B |
| ImageSharp | empty | 3,475.82 ns | 92.57 | 2,185 B |
| Lusamine | random | 55.63 ns | 1.00 | 184 B |
| ImageSharp | random | 5,295.91 ns | 95.22 | 1,368 B |
| Lusamine | text | 47.98 ns | 1.00 | 152 B |
| ImageSharp | text | 3,593.08 ns | 74.92 | 2,313 B |
| Lusamine | truncated-png | 30.53 ns | 1.00 | 256 B |
| ImageSharp | truncated-png | 5,532.30 ns | 181.27 | 1,600 B |
Extensibility
The decoder set is injectable for custom formats or testing:
var identifier = new ImageIdentifier(new IImageFormatDecoder[] { new MyFormatDecoder() });
| 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 is compatible. 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 | netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.1 is compatible. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | 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.1
- No dependencies.
-
net10.0
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.