LiteGif 0.2.0

The owner has unlisted this package. This could mean that the package is deprecated, has security vulnerabilities or shouldn't be used anymore.
dotnet add package LiteGif --version 0.2.0
                    
NuGet\Install-Package LiteGif -Version 0.2.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="LiteGif" Version="0.2.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="LiteGif" Version="0.2.0" />
                    
Directory.Packages.props
<PackageReference Include="LiteGif" />
                    
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 LiteGif --version 0.2.0
                    
#r "nuget: LiteGif, 0.2.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 LiteGif@0.2.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=LiteGif&version=0.2.0
                    
Install as a Cake Addin
#tool nuget:?package=LiteGif&version=0.2.0
                    
Install as a Cake Tool

LiteGif

A lightweight, pure-managed GIF encoder for .NET 10. Zero production dependencies, MIT license.

LiteGif writes a streaming GIF89a file from either pre-indexed frames (a global Rgb palette plus byte indices) or full-color Rgba32 frames that are quantized with an octree color quantizer. It is intentionally small, has no external runtime dependencies, and is easy to drop into a project.

Features

  • Targets net10.0 only; pure managed C# with zero production PackageReferences.
  • Indexed animated GIF encoding from a caller-supplied palette and per-frame indices.
  • Full-color animated GIF encoding from Rgba32 pixels (auto-quantized with an octree quantizer; by default the first frame's palette becomes the Global Color Table shared by all frames).
  • Optional per-frame Local Color Tables: pass a non-empty localPalette on AddFrame, or enable full-color auto multi-palette with UseGlobalColorTableFallback = false. Default (true) stays GCT-only; indexed mode never auto-generates an LCT.
  • Configurable loop count, default frame delay, transparent/background color index, and quantization settings.
  • Standalone LzwEncoder for raw GIF image-data compression.
  • Valid GIF89a output: header, Logical Screen Descriptor, Global Color Table, optional NETSCAPE2.0 loop extension, per-frame Graphics Control Extension, full-frame Image Descriptor, LZW image data, and 0x3B trailer.
  • Not thread-safe; create one GifEncoder per stream.

Installation

Add the package to a .NET 10 project:

dotnet add package LiteGif

Or reference the LiteGif project directly in your solution.

Quick start — indexed animated GIF

using LiteGif;
using System.IO;

int width = 320;
int height = 240;
int frameCount = 30;

var palette = new Rgb[256];
for (int i = 0; i < palette.Length; i++)
    palette[i] = new Rgb((byte)i, (byte)(i * 2), (byte)(i * 3));

var indices = new byte[width * height];
for (int i = 0; i < indices.Length; i++)
    indices[i] = (byte)(i % 256);

using var encoder = new GifEncoder(
    File.OpenWrite("indexed.gif"),
    width,
    height,
    palette,
    new GifEncoderOptions
    {
        LoopCount = 0,               // 0/negative = infinite, 1 = once, N>1 = N plays
        DefaultFrameDelayMs = 100,   // used when AddFrame is called without delayMs
        TransparentIndex = 0,        // optional: index 0 is transparent
        BackgroundIndex = 0
    });

for (int f = 0; f < frameCount; f++)
    encoder.AddFrame(indices, delayMs: 100);

// Dispose writes the GIF89a trailer.

Quick start — full-color animated GIF

using LiteGif;
using System.IO;

int width = 320;
int height = 240;
int frameCount = 30;

var pixels = new Rgba32[width * height];
for (int i = 0; i < pixels.Length; i++)
{
    int x = i % width;
    int y = i / width;
    pixels[i] = new Rgba32(
        (byte)(x * 255 / (width - 1)),
        (byte)(y * 255 / (height - 1)),
        128,
        255);
}

using var encoder = new GifEncoder(
    File.OpenWrite("fullcolor.gif"),
    width,
    height,
    new GifEncoderOptions
    {
        LoopCount = 0,
        DefaultFrameDelayMs = 100,
        Quantize = new QuantizeOptions
        {
            MaxColors = 128,          // 2–256
            AlphaThreshold = 128,     // pixels with A < this are treated as transparent
            Dither = true             // optional Floyd–Steinberg error diffusion (default false)
        }
    });

for (int f = 0; f < frameCount; f++)
    encoder.AddFrame(pixels, delayMs: 100);

Multi-frame palette policy (default): with the default UseGlobalColorTableFallback = true, the Global Color Table is built once from the first quantized frame (or from GlobalPalette if supplied) and later frames are remapped into that same palette — they are not re-quantized independently. This keeps multi-frame encoding fast and compact but can lose color fidelity when later frames introduce colors absent from the first frame. For per-frame palettes, set UseGlobalColorTableFallback = false (full-color frames then get a per-frame Local Color Table generated by the octree quantizer) or supply a better GlobalPalette up front.

Local Color Tables (optional)

A Global Color Table (GCT) is always written in the header. Each frame may additionally carry a Local Color Table (LCT) by passing a non-empty localPalette (2–256 Rgb entries) to AddFrame; that frame's indices are then interpreted against the local palette. With the default UseGlobalColorTableFallback = true, frames without a localPalette use the GCT and write no LCT — the existing quick-start behavior. A non-empty localPalette always writes an LCT (no equality elision).

Selective Local Color Table (indexed)

Mix GCT frames with per-frame LCT frames. localPalette is a trailing optional argument on every AddFrame overload.

using LiteGif;
using System.IO;

int width = 320;
int height = 240;

var globalPalette = new Rgb[256];
for (int i = 0; i < globalPalette.Length; i++)
    globalPalette[i] = new Rgb((byte)i, (byte)(i * 2), (byte)(i * 3));

var indices0 = new byte[width * height];
var indices1 = new byte[width * height];
var framePalette = new Rgb[]
{
    new(0, 0, 0), new(255, 255, 255), new(255, 0, 0), new(0, 255, 0)
};

using var encoder = new GifEncoder(
    File.OpenWrite("selective.gif"),
    width, height, globalPalette,
    new GifEncoderOptions { LoopCount = 0, DefaultFrameDelayMs = 100 });

// Frame 0: uses the Global Color Table (no LCT on the wire).
encoder.AddFrame(indices0, delayMs: 100);

// Frame 1: uses a per-frame Local Color Table (always written; no equality elision).
encoder.AddFrame(indices1, delayMs: 100, localPalette: framePalette);

Auto multi-palette (full-color)

Set UseGlobalColorTableFallback = false so full-color frames without a localPalette get a per-frame LCT generated by the octree quantizer. The first auto-GCT materializing frame (no pre-supplied GlobalPalette, GCT not yet built) builds the GCT from that frame and is GCT-only; later such frames re-quantize and emit an LCT. This path is for full-color only — indexed mode with fallback false requires a non-empty localPalette on every frame and throws InvalidOperationException otherwise.

using LiteGif;
using System.IO;

int width = 320;
int height = 240;

var pixels0 = new Rgba32[width * height];
var pixels1 = new Rgba32[width * height];
// ...fill pixels0 / pixels1...

using var encoder = new GifEncoder(
    File.OpenWrite("multipalette.gif"),
    width, height,
    new GifEncoderOptions
    {
        LoopCount = 0,
        DefaultFrameDelayMs = 100,
        UseGlobalColorTableFallback = false,
        Quantize = new QuantizeOptions { MaxColors = 128, AlphaThreshold = 128 }
    });

// Frame 0: builds the GCT, GCT-only.
encoder.AddFrame(pixels0, delayMs: 100);
// Later frames: re-quantized into a per-frame Local Color Table.
encoder.AddFrame(pixels1, delayMs: 100);

Size trade-off

Every LCT is padded to the next power of two ≥ its length (minimum 2) and costs 3 × padded entry count bytes after that frame's Image Descriptor — the same rule used for the GCT. Auto multi-palette re-quantizes each frame, so prefer it when frames differ enough to justify the extra tables.

Benchmarks

The benchmark harness lives in benchmarks/LiteGif.Benchmarks and is documented in benchmarks/LiteGif.Benchmarks/BenchmarksMethodology.md. It compares LiteGif against AnimatedGif 1.0.5 and ImageSharp 3.1.12 on pre-indexed 256-color frames written to Stream.Null. The comparison focuses on indexed framing + LZW cost; the ImageSharp lane uses a palette-locked Local encode but still performs palette matching (not raw index streaming), so its time includes quantization work LiteGif does not perform.

The harness has two lanes, both run from Program.cs:

  • Per-frame LCT (GifEncodeLctBenchmarks): a fair per-frame color-table lane where every frame carries its own Local Color Table on both encoders. This is the headline lane reported in the Results table below — it isolates per-frame color-table + LZW framing cost on both sides, so neither encoder benefits from a shared-palette shortcut.
  • GCT-only (GifEncodeBenchmarks): shared 256-entry palette, no per-frame Local Color Table. A secondary lane; no result numbers are reported for it here. Run it with the filter below to measure on your own machine.

Run the full matrix (both lanes):

dotnet run -c Release --project benchmarks/LiteGif.Benchmarks -- --filter '*'

Run a single lane:

# Per-frame LCT lane (the Results table below).
dotnet run -c Release --project benchmarks/LiteGif.Benchmarks -- --filter '*GifEncodeLctBenchmarks*'
# GCT-only lane.
dotnet run -c Release --project benchmarks/LiteGif.Benchmarks -- --filter '*GifEncodeBenchmarks*'

Results

BenchmarkDotNet ShortRun (3 iterations, in-process), .NET 10.0.9, X64. Pre-indexed 256-color frames, each carrying its own 256-entry Local Color Table, written to Stream.Null; LiteGif is the baseline lane. The full curated matrix is shown below; run it on your own machine for comparable numbers.

Method Resolution Frames Mean Ratio Allocated
LiteGif 320×240 30 8.41 ms 1.00 32.39 KB
AnimatedGif 320×240 30 37.89 ms 4.51 28.3 MB
ImageSharp 320×240 30 38.92 ms 4.63 109.4 KB
LiteGif 320×240 120 33.42 ms 1.00 126.82 KB
AnimatedGif 320×240 120 164.69 ms 4.93 113.1 MB
ImageSharp 320×240 120 155.33 ms 4.65 432.17 KB
LiteGif 1280×720 30 101.09 ms 1.00 33.17 KB
AnimatedGif 1280×720 30 964.06 ms 9.54 321.0 MB
ImageSharp 1280×720 30 385.96 ms 3.82 113.31 KB
LiteGif 1280×720 120 406.05 ms 1.00 129.55 KB
AnimatedGif 1280×720 120 3,798.39 ms 9.35 1.28 GB
ImageSharp 1280×720 120 1,548.05 ms 3.81 435.34 KB
LiteGif 1920×1080 30 226.64 ms 1.00 33.73 KB
AnimatedGif 1920×1080 30 11,755.17 ms 51.87 668.5 MB
ImageSharp 1920×1080 30 866.31 ms 3.82 113.45 KB

LiteGif's advantage grows with resolution. At 320×240 the ratio is ~4.5–4.9× against both competitors. At 1280×720 it widens to ~9.4× against AnimatedGif and ~3.8× against ImageSharp. At 1920×1080 it reaches ~52× against AnimatedGif (GDI+ per-frame Save overhead scales poorly with frame size) while staying ~3.8× against ImageSharp. LiteGif allocates a fixed ~33–130 KB per encode (scaling with frame count, not resolution); AnimatedGif allocates hundreds of MB to over 1 GB from per-frame GDI+ MemoryStream buffers. ImageSharp uses a palette-locked Local encode with no dither but still performs per-pixel palette matching (not raw index streaming); its time includes quantization work LiteGif does not perform. See the methodology doc for the full fairness caveat.

Notes

  • LoopCount: 0 or negative = infinite, 1 = play once, N>1 = N plays, max 65536.
  • DefaultFrameDelayMs is in milliseconds. GIF stores delays in centiseconds, so values are rounded half-up.
  • TransparentIndex and BackgroundIndex are validated against the GCT / effective palette size in the constructor; the per-frame transparentIndex argument on AddFrame is validated against the frame's active table (LCT if present, else GCT). BackgroundIndex is written into the Logical Screen Descriptor and is not re-validated against per-frame LCTs.
  • UseGlobalColorTableFallback defaults to true (GCT-only multi-frame). Set it to false for full-color auto per-frame LCTs; indexed mode then requires a non-empty localPalette on every frame.
  • QuantizeOptions.Dither = true enables Floyd–Steinberg error-diffusion dithering (classic 7/16, 3/16, 5/16, 1/16 kernel, left-to-right, full strength) during full-color index assignment, reducing banding when mapping to a small palette. Default is false (nearest-only). Dithering applies only to index mapping after the palette is built; it does not affect palette construction and does not apply to indexed-mode AddFrame overloads. The same 5-bit inverse color map is used for the per-pixel nearest lookup under dither.
  • Alpha values strictly less than QuantizeOptions.AlphaThreshold are mapped to the transparent index when one is configured.
  • Full-color mapping uses a 5-bit-per-channel (32³) inverse color map for speed. A pixel that is not exactly a cell midpoint may, at bin boundaries, map to a different palette slot than an exhaustive 24-bit linear nearest search would pick. This is the accepted trade-off for the O(1) per-pixel lookup.
  • ValidateIndices (default true): when true, the indexed AddFrame overloads scan every index byte and throw if any is >= the active palette size. Set it to false to skip that O(n) check for trusted indexed pipelines; the caller is then responsible for ensuring indices are in range (out-of-range indices produce undefined wire content). Length, delay, transparent-index, and LCT size validation still run regardless.
  • Dispose writes the 0x3B trailer only after at least one successful AddFrame; disposing an encoder that never received a frame writes nothing (no header, no trailer). It disposes the output stream unless leaveOpen: true is passed.

License

LiteGif is released under the MIT license. See the package metadata for details.

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