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.
- 🎨 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
npm install gif-tools
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();
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,
});
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`);
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();
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'
);
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,
}
);
The library provides powerful capabilities for reading, analyzing, and extracting data from existing GIF files, including metadata, frames, and color 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}`);
// 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!');
}
// 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})`
);
});
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`
);
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
// 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);
});
}
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
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
Creates a solid color GIF.
Returns: GifResult
- Rich wrapper with convenient conversion methods
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
Reads and parses GIF file information without extracting frames.
Parameters:
data
:Uint8Array
- Raw GIF file data
Returns: GifInfo
- Comprehensive information object with metadata
Validates whether the data represents a valid GIF file.
Parameters:
data
:Uint8Array
- Raw data to validate
Returns: boolean
- True if valid GIF format
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 usage
Types:
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
}
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 object
Low-level GIF writing with full control over the format.
Advanced color quantization using the median cut algorithm.
Output stream for collecting GIF data.
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);
}
}
- 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
This library is designed to work identically in both Node.js and browser environments:
- Primary: All helper functions return
GifResult
objects with adaptive methods - Raw Data:
toUint8Array()
always returnsUint8Array
(works everywhere) - Node.js:
toBuffer()
returns Node.jsBuffer
when available - Browser:
toBuffer()
gracefully falls back toUint8Array
whenBuffer
is not available
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');
}
This library works in all modern browsers that support:
Uint8Array
Map
andSet
- ES6 features
For older browsers, use a polyfill or transpile the code. Or maybe just don't use old browsers?
Requires Node.js 12+ for full ES6 support.
Contributions are welcome! Please read the contributing guidelines and ensure all tests pass before submitting a PR.
MIT License - see LICENSE file for details.
- Based on the GIF89a specification
- Implements LZW compression algorithm
- Uses median cut algorithm for color quantization