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-tools
Quick Start
Creating a Static GIF
import { createSolidColorGif } from 'gif-tools';
const gif = createSolidColorGif(100, 100, {
red: 255,
green: 0,
blue: 0,
});
gif.saveToFile('red-square.gif');
gif.download('red-square.gif');
const dataUrl = gif.toDataURL();
const blob = gif.toBlob();
Creating an Animated GIF
import { createAnimatedGif, createImageData } from 'gif-tools';
const frame1 = createImageData(
50,
50,
new Uint8Array(10000).fill(255)
);
const frame2 = createImageData(
50,
50,
new Uint8Array(10000).fill(0)
);
const gif = createAnimatedGif([frame1, frame2], {
delay: 500,
loops: 0,
maxColors: 256,
});
Working with Canvas (Browser)
import { canvasToImageData, createStaticGif } from 'gif-tools';
const canvas = document.getElementById('myCanvas') as HTMLCanvasElement;
const imageData = canvasToImageData(canvas);
const gif = createStaticGif(imageData);
gif.download('awesome.gif');
const dataUrl = gif.toDataURL();
const blob = gif.toBlob();
const objectUrl = gif.toObjectURL();
const img = document.createElement('img');
img.src = gif.toDataURL();
document.body.appendChild(img);
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';
const writer = new GifWriter();
const quantizer = new MedianCutQuantizer(16);
const indexedImage = quantizer.quantize(imageData);
writer
.writeHeader()
.writeLogicalScreen(imageData.width, imageData.height, {
colors: indexedImage.palette,
backgroundColorIndex: 0,
})
.writeImage(indexedImage, {
left: 0,
top: 0,
delay: 100,
transparentIndex: 0,
})
.writeTrailer();
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';
const checkerboard = createCheckerboardGif(
200,
200,
{ red: 255, green: 0, blue: 0 },
{ red: 0, green: 0, blue: 255 },
20
);
const gradient = createGradientGif(
300,
100,
{ red: 255, green: 0, blue: 0 },
{ red: 0, green: 0, blue: 255 },
'horizontal'
);
Creating Animated Gradients
Create stunning animated gradients with various effects:
import { createAnimatedGradientGif } from 'gif-tools';
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,
}
);
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,
}
);
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,
}
);
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';
import { readFileSync } from 'fs';
const gifData = readFileSync('animated.gif');
if (isValidGif(gifData)) {
console.log('Valid GIF file!');
}
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}`);
const reader = new GifReader(gifData);
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}`);
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);
document.body.appendChild(canvas);
}
});
if (reader.isAnimated()) {
console.log('This is an animated GIF!');
}
Color Analysis
const allColors = reader.getAllColors();
console.log(`Total colors in palette: ${allColors.length}`);
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;
console.log('Technical Information:');
console.log(` Interlaced frames: ${metadata.hasInterlacedFrames}`);
console.log(` Transparency: ${metadata.hasTransparency}`);
console.log(` Local color tables: ${metadata.hasLocalColorTables}`);
if (metadata.extensions.length > 0) {
console.log('Extensions found:');
metadata.extensions.forEach(ext => {
console.log(` • ${ext}`);
});
}
if (metadata.comments.length > 0) {
console.log('Comments:');
metadata.comments.forEach((comment, index) => {
console.log(` ${index + 1}: "${comment}"`);
});
}
if (metadata.xmpData) {
console.log('XMP Metadata found:');
console.log(metadata.xmpData);
}
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
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);
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);
const container = document.createElement('div');
container.appendChild(canvas);
container.appendChild(
document.createTextNode(`Frame ${index + 1} (${frame.delay}ms)`)
);
document.body.appendChild(container);
});
}
API Reference
Helper Functions
createStaticGif(imageData, options?): GifResult
Creates a static GIF from image data.
Parameters:
imageData: ImageData - RGBA image data
options: Optional configuration
maxColors: Maximum colors (default: 256)
globalColorTable: Global color table options
imageOptions: 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 frames
options: Optional configuration
maxColors: 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 pixels
height: number - Image height in pixels
startColor: RGBColor - Starting color {red, green, blue}
endColor: RGBColor - Ending color {red, green, blue}
options: Optional configuration
direction: '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 image
rotate: Colors rotate through the color spectrum
pulse: Gradient pulses in and out with sine wave
wave: 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);
const info = reader.getInfo();
const isAnimated = reader.isAnimated();
const frames = reader.getFrames();
const allColors = reader.getAllColors();
const dominant = reader.getDominantColors(5);
Types:
interface GifInfo {
width: number;
height: number;
frameCount: number;
loops: number;
globalColorTable?: Uint8Array;
backgroundColorIndex: number;
duration: number;
size: number;
version: string;
metadata: GifMetadata;
}
interface GifMetadata {
extensions: string[];
comments: string[];
hasInterlacedFrames: boolean;
hasLocalColorTables: boolean;
hasTransparency: boolean;
xmpData?: string;
technicalDetails: {
totalDataSize: number;
averageFrameSize: number;
compressionRatio?: number;
};
}
interface GifFrame {
imageData: ImageData;
delay: number;
disposal: DisposalMethod;
left: number;
top: number;
transparentIndex: number;
}
enum DisposalMethod {
DoNotDispose = 0,
RestoreToBackground = 1,
RestoreToPrevious = 2,
}
GifResult
Rich wrapper around GIF data with convenient conversion methods:
const gif = createStaticGif(imageData);
gif.toUint8Array();
gif.toBuffer();
gif.toDataURL();
gif.toBlob();
gif.toObjectURL();
gif.toStream();
gif.download('file.gif');
await gif.saveToFile('path.gif');
gif.size;
gif.sizeFormatted;
gif.mimeType;
gif.extension;
gif.isValidGif();
gif.getInfo();
GifWriter
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
GifResult objects with adaptive
methods
- Raw Data:
toUint8Array() always returns Uint8Array (works everywhere)
- Node.js:
toBuffer() returns Node.js Buffer when available
- Browser:
toBuffer() gracefully falls back to Uint8Array when Buffer
is not available
Environment Detection
const gif = createStaticGif(imageData);
const rawData = gif.toUint8Array();
const bufferOrArray = gif.toBuffer();
if (typeof document !== 'undefined') {
gif.download('image.gif');
const dataUrl = gif.toDataURL();
} else {
await gif.saveToFile('image.gif');
}
Browser Compatibility
This library works in all modern browsers that support:
Uint8Array
Map and Set
- 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