gif-tools
v1.0.0
Published
A robust, zero-dependency TypeScript library for creating GIF files with support for both static and animated GIFs. Built with modern TypeScript features and designed to work in both Node.js and browser environments.
Downloads
28
Maintainers
Readme
Modern TypeScript GIF Tools
A robust, zero-dependency TypeScript library for creating GIF files with support for both static and animated GIFs. Built with modern TypeScript features and designed to work in both Node.js and browser environments.
Features
- 🎨 Static and Animated GIFs: Create both single-frame and multi-frame GIFs
- 📖 GIF Reading & Analysis: Read, parse, and extract frames from existing GIF files
- 🔍 Metadata Extraction: Parse GIF extensions, comments, and embedded data
- 🎞️ Frame Extraction: Extract individual frames as ImageData objects
- 🌈 Color Quantization: Advanced median-cut algorithm for optimal color reduction
- 🔧 Modern TypeScript: Full type safety with comprehensive error handling
- 📦 Zero Dependencies: No external dependencies, works everywhere
- 🏃♂️ Performance: Efficient LZW compression and decompression algorithms
- 🎯 Cross-Platform: Works in Node.js, browsers, and other JavaScript environments
Installation
npm install gif-toolsInstalling globally (or with npx) also exposes a gif-tools command. See
Command-line interface below.
Quick Start
Creating a Static GIF
import { createSolidColorGif } from 'gif-tools';
// Create a 100x100 red GIF (returns a GifResult)
const gif = createSolidColorGif(100, 100, {
red: 255,
green: 0,
blue: 0,
});
// In Node.js, you can save the GIF to a file
gif.saveToFile('red-square.gif');
// In the browser, you can download the GIF:
gif.download('red-square.gif');
// Or you can get the GIF as a data URL:
const dataUrl = gif.toDataURL();
// Or you can get the GIF as a blob:
const blob = gif.toBlob();Creating an Animated GIF
import { createAnimatedGif, createImageData } from 'gif-tools';
// Build a couple of solid color frames
const frame1 = createImageData(
50,
50,
new Uint8Array(10000).fill(255) // White
);
const frame2 = createImageData(
50,
50,
new Uint8Array(10000).fill(0) // Black
);
// Create animated GIF (returns a GifResult)
const gif = createAnimatedGif([frame1, frame2], {
// 500ms between frames (default is 100ms)
delay: 500,
// Loop forever (number of loops, 0 = infinite)
loops: 0,
// Use up to 256 colors (recommended for animations)
maxColors: 256,
});Working with Canvas (Browser)
import { canvasToImageData, createStaticGif } from 'gif-tools';
// Get canvas element
const canvas = document.getElementById('myCanvas') as HTMLCanvasElement;
// Convert canvas content to GIF
const imageData = canvasToImageData(canvas);
const gif = createStaticGif(imageData);
// The GifResult object offers convenience functions:
gif.download('awesome.gif'); // Trigger save dialog (browser only)
const dataUrl = gif.toDataURL(); // For <img> src attributes
const blob = gif.toBlob(); // For fetch requests
const objectUrl = gif.toObjectURL(); // For custom links
// Display in an image element
const img = document.createElement('img');
img.src = gif.toDataURL();
document.body.appendChild(img);
// Get file info
console.log(`Created ${gif.sizeFormatted} GIF`);Advanced Usage
Using the Low-Level API
You can use the low-level API to create and manipulate GIFs with full control over the format. This allows you to do things like compose GIFs dynamically from multiple image sources, adjust the delay between frames, add transparency, manage global color tables, and more.
import {
ByteArrayOutputStream,
GifWriter,
IndexedImage,
MedianCutQuantizer,
} from 'gif-tools';
// Create a custom GIF with full control
const writer = new GifWriter();
// Create indexed image with quantization
const quantizer = new MedianCutQuantizer(16); // Reduce to 16 colors
const indexedImage = quantizer.quantize(imageData);
// Write GIF with custom options
writer
.writeHeader()
.writeLogicalScreen(imageData.width, imageData.height, {
colors: indexedImage.palette,
backgroundColorIndex: 0,
})
.writeImage(indexedImage, {
left: 0,
top: 0,
delay: 100,
transparentIndex: 0, // Make color 0 transparent
})
.writeTrailer();
// Get the raw data as Uint8Array
const gif = writer.toUint8Array();Creating Pattern GIFs
You can also create GIFs with customized pattern and gradient presets easily.
import { createCheckerboardGif, createGradientGif } from 'gif-tools';
// Checkerboard pattern
const checkerboard = createCheckerboardGif(
200,
200,
{ red: 255, green: 0, blue: 0 }, // Red
{ red: 0, green: 0, blue: 255 }, // Blue
20 // 20px squares
);
// Gradient
const gradient = createGradientGif(
300,
100,
{ red: 255, green: 0, blue: 0 }, // Start: Red
{ red: 0, green: 0, blue: 255 }, // End: Blue
'horizontal'
);Creating Animated Gradients
Create stunning animated gradients with various effects:
import { createAnimatedGradientGif } from 'gif-tools';
// Shifting gradient - slides across the image
const shiftGradient = createAnimatedGradientGif(
200,
100,
{ red: 255, green: 0, blue: 0 },
{ red: 0, green: 0, blue: 255 },
{
direction: 'horizontal',
animationType: 'shift',
frames: 20,
delay: 100,
loops: 0,
intensity: 1.0,
}
);
// Pulsing gradient - pulses in and out
const pulseGradient = createAnimatedGradientGif(
150,
150,
{ red: 0, green: 255, blue: 0 },
{ red: 255, green: 0, blue: 255 },
{
direction: 'diagonal',
animationType: 'pulse',
frames: 30,
delay: 80,
intensity: 0.8,
}
);
// Wave gradient - wavy distortion effect
const waveGradient = createAnimatedGradientGif(
200,
100,
{ red: 255, green: 255, blue: 0 },
{ red: 0, green: 255, blue: 255 },
{
direction: 'vertical',
animationType: 'wave',
frames: 25,
delay: 120,
intensity: 0.6,
}
);
// Rotating colors - cycles through color spectrum
const rotateGradient = createAnimatedGradientGif(
150,
150,
{ red: 255, green: 0, blue: 0 },
{ red: 0, green: 0, blue: 255 },
{
animationType: 'rotate',
frames: 40,
delay: 75,
intensity: 1.0,
}
);Reading and Analyzing GIFs
The library provides powerful capabilities for reading, analyzing, and extracting data from existing GIF files, including metadata, frames, and color information.
Reading GIF Information
import { GifReader, isValidGif, readGifInfo } from 'gif-tools';
// Load GIF data (Node.js example)
import { readFileSync } from 'fs';
const gifData = readFileSync('animated.gif');
// Quick validation
if (isValidGif(gifData)) {
console.log('Valid GIF file!');
}
// Get basic information
const info = readGifInfo(gifData);
console.log(`Dimensions: ${info.width}×${info.height}`);
console.log(`Frames: ${info.frameCount}`);
console.log(`Duration: ${info.duration}ms`);
console.log(`File size: ${info.size} bytes`);
console.log(`Version: GIF${info.version}`);
console.log(`Loops: ${info.loops === 0 ? 'infinite' : info.loops}`);Extracting Frames
// Create a reader instance for advanced operations
const reader = new GifReader(gifData);
// Extract all frames
const frames = reader.getFrames();
frames.forEach((frame, index) => {
console.log(`Frame ${index + 1}:`);
console.log(` Size: ${frame.imageData.width}×${frame.imageData.height}`);
console.log(` Delay: ${frame.delay}ms`);
console.log(` Position: (${frame.left}, ${frame.top})`);
console.log(` Disposal: ${frame.disposal}`);
// Convert frame to canvas (browser)
if (typeof document !== 'undefined') {
const canvas = document.createElement('canvas');
canvas.width = frame.imageData.width;
canvas.height = frame.imageData.height;
const ctx = canvas.getContext('2d');
ctx.putImageData(frame.imageData, 0, 0);
// Now you can save or display the frame
document.body.appendChild(canvas);
}
});
// Check if the GIF is animated
if (reader.isAnimated()) {
console.log('This is an animated GIF!');
}Color Analysis
// Extract color information
const allColors = reader.getAllColors();
console.log(`Total colors in palette: ${allColors.length}`);
// Get dominant colors (most common)
const dominantColors = reader.getDominantColors(5);
dominantColors.forEach((color, index) => {
console.log(
`Color ${index + 1}: rgb(${color.red}, ${color.green}, ${color.blue})`
);
});Metadata and Extensions
The library can parse and extract various types of metadata and extensions found in GIF files:
const info = readGifInfo(gifData);
const metadata = info.metadata;
// Technical flags
console.log('Technical Information:');
console.log(` Interlaced frames: ${metadata.hasInterlacedFrames}`);
console.log(` Transparency: ${metadata.hasTransparency}`);
console.log(` Local color tables: ${metadata.hasLocalColorTables}`);
// Extensions found in the file
if (metadata.extensions.length > 0) {
console.log('Extensions found:');
metadata.extensions.forEach(ext => {
console.log(` • ${ext}`);
});
}
// Embedded comments
if (metadata.comments.length > 0) {
console.log('Comments:');
metadata.comments.forEach((comment, index) => {
console.log(` ${index + 1}: "${comment}"`);
});
}
// XMP metadata (if present)
if (metadata.xmpData) {
console.log('XMP Metadata found:');
console.log(metadata.xmpData);
}
// Technical details
console.log('Technical Details:');
console.log(
` Average frame size: ${metadata.technicalDetails.averageFrameSize} bytes`
);
console.log(
` Total data size: ${metadata.technicalDetails.totalDataSize} bytes`
);Supported Extensions
The library can detect and parse these common GIF extensions:
- Netscape 2.0: Animation control (loop count)
- XMP Metadata: Adobe XMP metadata
- ICC Color Profiles: Color management data
- Comment Extensions: Text comments embedded in the file
- Plain Text Extensions: Text overlay information
- Application Extensions: Various proprietary extensions (MAGPIE, Adobe, etc.)
- Private Extensions: Custom application-specific data
Browser Usage for File Upload
// Handle file uploads in the browser
function handleFileUpload(event: Event) {
const file = (event.target as HTMLInputElement).files?.[0];
if (!file) return;
const reader = new FileReader();
reader.onload = e => {
const arrayBuffer = e.target?.result as ArrayBuffer;
const uint8Array = new Uint8Array(arrayBuffer);
if (isValidGif(uint8Array)) {
const gifReader = new GifReader(uint8Array);
const info = gifReader.getInfo();
console.log('Uploaded GIF info:', info);
// Extract and display frames
const frames = gifReader.getFrames();
displayFrames(frames);
} else {
console.error('Invalid GIF file');
}
};
reader.readAsArrayBuffer(file);
}
function displayFrames(frames: GifFrame[]) {
frames.forEach((frame, index) => {
const canvas = document.createElement('canvas');
canvas.width = frame.imageData.width;
canvas.height = frame.imageData.height;
const ctx = canvas.getContext('2d')!;
ctx.putImageData(frame.imageData, 0, 0);
// Add frame info
const container = document.createElement('div');
container.appendChild(canvas);
container.appendChild(
document.createTextNode(`Frame ${index + 1} (${frame.delay}ms)`)
);
document.body.appendChild(container);
});
}Command-line interface
gif-tools ships with a CLI that exposes the bulk of the library from the
terminal — useful for one-off conversions, scripting, or experimenting without
writing TypeScript. It runs on Node and stays zero-dependency.
Installing
# project-local
npx gif-tools --help
# globally
npm install -g gif-tools
gif-tools --helpQuick recipes
# Inspect a GIF
gif-tools info input.gif --dominant 5
# Create a 64×64 red square
gif-tools create solid red.gif --width 64 --height 64 --color "#ff0000"
# Animated gradient sweep
gif-tools create animated-gradient sweep.gif \
-w 200 -h 100 --start "#ff0000" --end "#0000ff" \
--animation-type shift --frames 24 --delay 80
# Resize to 50% of original
gif-tools resize input.gif -o small.gif --scale 0.5
# Apply per-frame transforms
gif-tools crop input.gif -o crop.gif --x 10 --y 10 --width 100 --height 100
gif-tools rotate input.gif -o rot.gif --angle 90
gif-tools flip input.gif -o flip.gif --horizontal --vertical
gif-tools adjust input.gif -o tweak.gif --brightness -0.1 --contrast 0.2 --saturation 0.3
gif-tools blur input.gif -o soft.gif --radius 2
# Animation timing
gif-tools reverse input.gif -o reversed.gif
gif-tools speed input.gif -o fast.gif --multiplier 2 --min-delay 20
# Compression: re-quantize palette and/or downscale
gif-tools optimize input.gif -o smaller.gif --max-colors 64 --scale 0.5
# Extract every frame as its own GIF
gif-tools extract input.gif --output-dir frames/Commands
| Command | What it does |
|--------------------------|--------------|
| info <input> | Print dimensions, frame count, duration, extensions, dominant colors |
| create <type> <output> | Generate a new GIF — see "Create types" below |
| resize <input> | Resize frames by --width/--height and/or --scale |
| crop <input> | Crop to --x --y --width --height |
| rotate <input> | Rotate --angle 90|180|270 |
| flip <input> | --horizontal and/or --vertical |
| adjust <input> | --brightness --contrast --saturation --hue |
| blur <input> | Box blur with --radius N |
| reverse <input> | Reverse frame order |
| speed <input> | --multiplier 2 (faster) or 0.5 (slower); add --min-delay/--max-delay |
| optimize <input> | Re-encode with --max-colors and optional --scale |
| extract <input> | Write each frame as a separate GIF to --output-dir |
Create types
| Type | Required | Notable options |
|---------------------|-------------------------------------------------------------------------|-----------------|
| solid | --width --height --color | — |
| gradient | --width --height --start --end | --direction horizontal|vertical|diagonal |
| animated-gradient | --width --height --start --end | --animation-type shift|rotate|pulse|wave --frames --delay --loops --intensity --direction --max-colors |
| checkerboard | --width --height --color1 --color2 | --check-size |
| noise | --width --height | --type white|perlin|simplex --scale --seed --colors |
| fractal | --width --height | --type mandelbrot|julia|sierpinski --iterations --zoom --center-x --center-y --colors |
| geometric | --width --height | --shape circles|squares|triangles|hexagons --count --size-variation --colors --background |
| spiral | --width --height | --type archimedean|logarithmic|fibonacci --turns --thickness --colors |
Conventions
- Colors accept
r,g,b(e.g.255,128,0),#rrggbb(e.g.#ff8000), or#rgb(e.g.#f80). - Color lists (
--colorson pattern commands) use;as the separator since,is reserved for RGB triplets:"255,0,0;0,255,0;0,0,255". - Output files are written via
--output/-ofor edit commands and as the first positional after the create type forcreate. --quiet(-q) silences status output.--help(-h) prints usage. SetDEBUG=1in the environment to include stack traces on errors.
API Reference
Helper Functions
createStaticGif(imageData, options?): GifResult
Creates a static GIF from image data.
Parameters:
imageData:ImageData- RGBA image dataoptions: Optional configurationmaxColors: Maximum colors (default: 256)globalColorTable: Global color table optionsimageOptions: Image-specific options
Returns: GifResult - Rich wrapper with convenient conversion methods
createAnimatedGif(frames, options?): GifResult
Creates an animated GIF from multiple frames.
Parameters:
frames:ImageData[]- Array of image framesoptions: Optional configurationmaxColors: Maximum colors (default: 256)delay: Delay between frames in ms (default: 100)loops: Number of loops, 0 for infinite (default: 0)
Returns: GifResult - Rich wrapper with convenient conversion methods
createSolidColorGif(width, height, color): GifResult
Creates a solid color GIF.
Returns: GifResult - Rich wrapper with convenient conversion methods
createAnimatedGradientGif(width, height, startColor, endColor, options?): GifResult
Creates an animated gradient GIF with various animation effects.
Parameters:
width:number- Image width in pixelsheight:number- Image height in pixelsstartColor:RGBColor- Starting color{red, green, blue}endColor:RGBColor- Ending color{red, green, blue}options: Optional configurationdirection:'horizontal' | 'vertical' | 'diagonal'(default: 'horizontal')animationType:'shift' | 'rotate' | 'pulse' | 'wave'(default: 'shift')frames: Number of animation frames (default: 20)delay: Delay between frames in ms (default: 100)loops: Number of loops, 0 for infinite (default: 0)intensity: Animation strength, 0-1 (default: 1.0)
Animation Types:
shift: Gradient slides/shifts across the imagerotate: Colors rotate through the color spectrumpulse: Gradient pulses in and out with sine wavewave: Wave-like distortion creates flowing effect
Returns: GifResult - Rich wrapper with convenient conversion methods
readGifInfo(data: Uint8Array): GifInfo
Reads and parses GIF file information without extracting frames.
Parameters:
data:Uint8Array- Raw GIF file data
Returns: GifInfo - Comprehensive information object with metadata
isValidGif(data: Uint8Array): boolean
Validates whether the data represents a valid GIF file.
Parameters:
data:Uint8Array- Raw data to validate
Returns: boolean - True if valid GIF format
Core Classes
GifReader
Advanced GIF reading and analysis class:
const reader = new GifReader(gifData);
// Basic information
const info = reader.getInfo(); // Complete GIF information
const isAnimated = reader.isAnimated(); // Check if animated
// Frame extraction
const frames = reader.getFrames(); // Extract all frames
// frames[0].imageData // ImageData for frame 0
// frames[0].delay // Frame delay in milliseconds
// frames[0].disposal // Disposal method
// frames[0].left, frames[0].top // Frame position
// frames[0].transparentIndex // Transparent color index
// Color analysis
const allColors = reader.getAllColors(); // Complete color palette
const dominant = reader.getDominantColors(5); // Top N colors by usageTypes:
interface GifInfo {
width: number; // Image dimensions
height: number;
frameCount: number; // Number of frames
loops: number; // Loop count (0 = infinite)
globalColorTable?: Uint8Array; // Global color palette
backgroundColorIndex: number; // Background color
duration: number; // Total duration in milliseconds
size: number; // File size in bytes
version: string; // GIF version ('87a' or '89a')
metadata: GifMetadata; // Comprehensive metadata
}
interface GifMetadata {
extensions: string[]; // List of extensions found
comments: string[]; // Embedded comments
hasInterlacedFrames: boolean; // Uses interlacing
hasLocalColorTables: boolean; // Has local color tables
hasTransparency: boolean; // Uses transparency
xmpData?: string; // XMP metadata if present
technicalDetails: {
totalDataSize: number; // File size
averageFrameSize: number; // Average bytes per frame
compressionRatio?: number; // Compression efficiency
};
}
interface GifFrame {
imageData: ImageData; // Frame pixel data
delay: number; // Display duration in milliseconds
disposal: DisposalMethod; // How to handle this frame
left: number; // X position within GIF canvas
top: number; // Y position within GIF canvas
transparentIndex: number; // Transparent color (-1 if none)
}
enum DisposalMethod {
DoNotDispose = 0, // Keep frame on screen
RestoreToBackground = 1, // Clear frame area
RestoreToPrevious = 2, // Restore to previous frame
}GifResult
Rich wrapper around GIF data with convenient conversion methods:
const gif = createStaticGif(imageData);
// Raw data access
gif.toUint8Array(); // Always returns Uint8Array
gif.toBuffer(); // Node.js Buffer or Uint8Array in browser
// Web-friendly formats
gif.toDataURL(); // data:image/gif;base64,... for <img> src
gif.toBlob(); // Blob for file operations (browser only)
gif.toObjectURL(); // blob:... URL for links (browser only)
gif.toStream(); // ReadableStream (when available)
// File operations
gif.download('file.gif'); // Trigger download (browser only)
await gif.saveToFile('path.gif'); // Save to file (Node.js only)
// Properties and utilities
gif.size; // Size in bytes
gif.sizeFormatted; // Human-readable size (e.g., "2.5 KB")
gif.mimeType; // "image/gif"
gif.extension; // "gif"
gif.isValidGif(); // Validate GIF signature
gif.getInfo(); // Comprehensive info objectGifWriter
Low-level GIF writing with full control over the format.
MedianCutQuantizer
Advanced color quantization using the median cut algorithm.
ByteArrayOutputStream
Output stream for collecting GIF data.
Error Handling
The library provides comprehensive error handling with specific error types:
import { GifEncodingError, GifValidationError } from 'gif-tools';
try {
const gif = createSolidColorGif(-10, 10, { red: 256, green: 0, blue: 0 });
} catch (error) {
if (error instanceof GifValidationError) {
console.error('Invalid input:', error.message);
} else if (error instanceof GifEncodingError) {
console.error('Encoding failed:', error.message);
}
}Performance Tips
- Reduce Colors: Use fewer colors for smaller file sizes
- Optimize Frames: Remove duplicate frames in animations
- Size Appropriately: Smaller dimensions = faster processing
- Batch Operations: Process multiple GIFs in sequence for better performance
Cross-Platform Compatibility
This library is designed to work identically in both Node.js and browser environments:
Output Format
- Primary: All helper functions return
GifResultobjects with adaptive methods - Raw Data:
toUint8Array()always returnsUint8Array(works everywhere) - Node.js:
toBuffer()returns Node.jsBufferwhen available - Browser:
toBuffer()gracefully falls back toUint8ArraywhenBufferis not available
Environment Detection
const gif = createStaticGif(imageData);
// Raw data - works everywhere
const rawData = gif.toUint8Array(); // Always Uint8Array
// Adaptive - returns appropriate type for environment
const bufferOrArray = gif.toBuffer(); // Buffer in Node.js, Uint8Array in browser
// Environment-specific methods
if (typeof document !== 'undefined') {
// Browser environment
gif.download('image.gif');
const dataUrl = gif.toDataURL();
} else {
// Node.js environment
await gif.saveToFile('image.gif');
}Browser Compatibility
This library works in all modern browsers that support:
Uint8ArrayMapandSet- ES6 features
For older browsers, use a polyfill or transpile the code. Or maybe just don't use old browsers?
Node.js Compatibility
Requires Node.js 12+ for full ES6 support.
Contributing
Contributions are welcome! Please read the contributing guidelines and ensure all tests pass before submitting a PR.
License
MIT License - see LICENSE file for details.
Prior Work and Acknowledgments
- Based on the GIF89a specification
- Implements LZW compression algorithm
- Uses median cut algorithm for color quantization
