SharpApng 0.3.0

dotnet add package SharpApng --version 0.3.0
                    
NuGet\Install-Package SharpApng -Version 0.3.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="SharpApng" Version="0.3.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="SharpApng" Version="0.3.0" />
                    
Directory.Packages.props
<PackageReference Include="SharpApng" />
                    
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 SharpApng --version 0.3.0
                    
#r "nuget: SharpApng, 0.3.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 SharpApng@0.3.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=SharpApng&version=0.3.0
                    
Install as a Cake Addin
#tool nuget:?package=SharpApng&version=0.3.0
                    
Install as a Cake Tool

SharpApng

Imaging for .NET, with no dependencies at all.

Reads and writes animated PNG. Decodes PNG, JPEG, GIF and BMP. Resizes. Every image is RGBA — 8 or 16 bits per channel — because transparency is the point. Zero package dependencies, no native libraries to deploy, identical behaviour on Windows, Linux and macOS. Trimming- and NativeAOT-safe.

Writing

using SharpApng;

using var file = File.Create("walk.png");
using var writer = new ApngWriter(file, width: 320, height: 240);

foreach (RgbaImage frame in frames)
    writer.AddFrame(frame, delayMilliseconds: 40);

writer.Complete();

Frames go in at full canvas size. The writer works out which rectangle changed and stores only that, and folds identical consecutive frames into the previous frame's delay. On a 320×320, 24-frame animation with a small moving sprite, that came out 91.9 % smaller than storing whole frames.

Options:

var options = new ApngWriterOptions
{
    LoopCount = 0,          // 0 plays forever
    OptimizeFrames = true,  // crop to the changed region (default)
    CompressionLevel = CompressionLevel.SmallestSize,
};

A stronger compressor

The runtime's zlib compresses every image unless you supply a ZlibCompressor: it receives an image's filtered scanlines and returns a complete zlib stream. With ZopfliSharp, one line:

var options = new ApngWriterOptions { Compressor = data => Zopfli.Compress(data, ZopfliFormat.Zlib) };

PngCodec.Encode, EncodeFile and EncodeToArray take one too. The library itself stays free of dependencies: the compressor is yours to reference, and without one the output is byte-for-byte unchanged. Zopfli typically gives files 6–9 % smaller than SmallestSize, at a steep cost in time. Raw deflate or gzip output is refused rather than written into a PNG no decoder could read.

Reading

AnimationDocument document = ApngReader.ReadFile("walk.png");

// Frames come back as the file stores them: cropped sub-images with offsets and blend modes.
// RenderFrames replays them into the full-canvas images a viewer actually shows.
foreach (RgbaImage rendered in document.RenderFrames())
    Process(rendered);

A still PNG reads as a one-frame animation, so callers need no special case.

Pixels

var image = new RgbaImage(320, 240);                        // 8 bits per channel
var deep  = new RgbaImage(320, 240, PixelFormat.Rgba64);    // 16 bits per channel

Span<Rgba32> pixels = image.AsRgba32();     // direct, allocation-free
pixels[0] = new Rgba32(255, 0, 0);

RgbaImage wrapped = RgbaImage.Wrap(myBuffer, 320, 240);     // no copy

Rgba32 and Rgba64 are sequential structs whose field order matches memory order, so a byte[] can be reinterpreted as either with no conversion. Alpha is never premultiplied.

Decoding accepts any PNG the specification allows — every colour type and bit depth, palettes, tRNS transparency, Adam7 interlacing — plus JPEG, GIF and BMP, and normalises everything to RGBA. ImageLoader picks the decoder from the data itself rather than the file extension.

Resizing

RgbaImage thumbnail = RgbaImageOps.ResizeToFit(source, 128);
RgbaImage exact     = RgbaImageOps.Resize(source, 320, 240, ResampleFilter.Lanczos3);

Filtering runs on premultiplied alpha internally, so transparent pixels cannot bleed their colour into the edge — no dark fringe around resized cut-outs. Works on any RgbaImage, whatever format it came from.

Decoding JPEG

RgbaImage photo = JpegCodec.DecodeFile("upload.jpg");
JpegInfo info = JpegCodec.ReadInfo(bytes);   // size and orientation, no pixel decoding

Baseline and progressive, greyscale and YCbCr, any chroma subsampling. EXIF orientation is applied by default, matching every browser and viewer. Arithmetic, lossless, hierarchical, 12-bit and CMYK files are refused with a message naming what was found rather than decoded approximately. There is no JPEG encoder.

Converting GIF

AnimationDocument source = AnimationLoader.LoadFile("input.gif");
ApngWriter.WriteFile(source, "output.png");

GIF's animation model maps almost exactly onto APNG's, so timing and layering are preserved rather than approximated: the three disposal methods match, index transparency behaves as BlendOp.Over with binary alpha, and hundredths-of-a-second delays land on delay_den = 100 with no rounding.

Two deliberate departures from the letter of the GIF specification, both to match what browsers actually do: delays of 0 or 1 hundredths are raised to 100 ms, and "restore to background" clears to transparent. Both are configurable through GifReaderOptions.

Since the output is RGBA, GifReaderOptions.TransparentColor can erase a colour the GIF was never able to mark transparent.

LZW carries no licensing burden: the Unisys patent expired in 2003–2004.

Notes

Encoding always emits colour type 6, non-interlaced. Writing exactly one layout is deliberate: it is what makes an animation's frames incapable of disagreeing with the header that governs them.

Frame optimisation uses BlendOp.Source on a cropped rectangle rather than blanking unchanged pixels for BlendOp.Over. Over can never lower the alpha already on the canvas, so it cannot express a frame that erases something — Source is exact in every case.

16 bits per channel buys precision, not dynamic range: it is not HDR, and browsers truncate it to 8 bits when compositing. It is worth it as a working or archival format.

APNG plays in Chrome 59+, Firefox 3+, Safari 8+ and Edge 79+. Viewers that do not understand it show the default image, so a file degrades to a still rather than to nothing.


Source, documentation and a command line tool · MIT

Product Compatible and additional computed target framework versions.
.NET 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • 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.

Version Downloads Last Updated
0.3.0 59 11/6/2026
0.2.0 224 10/17/2026

Adds ZlibCompressor, so a caller can supply its own zlib compressor, such as ZopfliSharp, for files typically 6-9 % smaller: ApngWriterOptions.Compressor covers every IDAT and fdAT, and new PngCodec.Encode, EncodeFile and EncodeToArray overloads cover still images. The library still has no dependencies, and without a compressor its output is byte-for-byte unchanged.