gif-tools
Version:
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.
480 lines (479 loc) • 17.4 kB
TypeScript
/**
* Utility Functions for GIF Processing
*
* This module provides essential utility functions for GIF creation, validation,
* and data manipulation. These functions handle low-level operations like
* validation, color operations, and binary data processing.
*/
import { ColorIntensity, RGBColor, ImageDimensions } from './types.js';
/**
* GIF file signature bytes - the magic number "GIF" that identifies GIF files.
* Used for validating that binary data represents a valid GIF file.
*/
export declare const GIF_SIGNATURE: Uint8Array;
/**
* GIF version 89a bytes - identifies GIF89a format which supports animation and transparency.
* This is the most commonly used GIF version for modern applications.
*/
export declare const GIF_VERSION_89A: Uint8Array;
/**
* GIF trailer byte - marks the end of a GIF file.
* Every valid GIF file must end with this byte.
*/
export declare const GIF_TRAILER = 59;
/**
* Maximum number of colors supported in a GIF palette.
* GIF format limitation due to 8-bit color indexing.
*/
export declare const MAX_COLORS = 256;
/**
* Maximum LZW code value for GIF compression.
* This represents the highest code value (4095) used in LZW compression algorithm.
*/
export declare const MAX_CODE_VALUE = 4095;
/**
* Netscape application identifier for animation extensions.
* Used to identify Netscape-style animation controls in GIF files.
*/
export declare const NETSCAPE_APPLICATION_IDENTIFIER: Uint8Array;
/**
* Netscape application authentication code for animation extensions.
* Specifies version "2.0" of the Netscape animation extension format.
*/
export declare const NETSCAPE_APPLICATION_AUTHENTICATION_CODE: Uint8Array;
/**
* Validates image dimensions for GIF creation.
*
* Ensures width and height are positive integers within GIF format limits.
* GIF format supports dimensions up to 65535 pixels in each direction.
*
* @param dimensions - Object containing width and height
* @param dimensions.width - Image width in pixels
* @param dimensions.height - Image height in pixels
* @throws {GifValidationError} When dimensions are invalid or out of range
*
* @example
* ```typescript
* // Valid dimensions
* validateDimensions({ width: 800, height: 600 }); // OK
*
* // These will throw errors
* validateDimensions({ width: -100, height: 200 }); // Negative width
* validateDimensions({ width: 100.5, height: 200 }); // Non-integer
* validateDimensions({ width: 70000, height: 200 }); // Too large
* ```
*/
export declare function validateDimensions(dimensions: ImageDimensions): void;
/**
* Validates a single color intensity value (0-255).
*
* Ensures a color component value is a valid integer within the RGB range.
* Used for validating individual red, green, and blue color components.
*
* @param value - Color intensity value to validate
* @param name - Name of the color component for error messages (default: 'color')
* @throws {GifValidationError} When value is not an integer or outside 0-255 range
*
* @example
* ```typescript
* // Valid values
* validateColorIntensity(255, 'red'); // OK
* validateColorIntensity(0, 'green'); // OK
* validateColorIntensity(128, 'blue'); // OK
*
* // These will throw errors
* validateColorIntensity(-1, 'red'); // Below range
* validateColorIntensity(256, 'green'); // Above range
* validateColorIntensity(128.5, 'blue'); // Not integer
* ```
*/
export declare function validateColorIntensity(value: number, name?: string): void;
/**
* Validates an RGB color object.
*
* Ensures all color components (red, green, blue) are valid integers
* within the 0-255 range. This is a comprehensive validation for complete RGB colors.
*
* @param color - RGB color object to validate
* @param color.red - Red component (0-255)
* @param color.green - Green component (0-255)
* @param color.blue - Blue component (0-255)
* @throws {GifValidationError} When any color component is invalid
*
* @example
* ```typescript
* // Valid colors
* validateRGBColor({ red: 255, green: 128, blue: 0 }); // Orange
* validateRGBColor({ red: 0, green: 0, blue: 0 }); // Black
* validateRGBColor({ red: 255, green: 255, blue: 255 }); // White
*
* // These will throw errors
* validateRGBColor({ red: -1, green: 128, blue: 255 }); // Invalid red
* validateRGBColor({ red: 255, green: 256, blue: 0 }); // Invalid green
* validateRGBColor({ red: 128.5, green: 0, blue: 255 }); // Non-integer red
* ```
*/
export declare function validateRGBColor(color: RGBColor): void;
/**
* Validates GIF palette data format and constraints.
*
* Ensures palette data is properly formatted as RGB triplets and contains
* a valid number of colors within GIF format limitations.
*
* @param palette - Palette data as RGB triplets (3 bytes per color)
* @throws {GifValidationError} When palette format or size is invalid
*
* @example
* ```typescript
* // Valid palettes
* const palette1 = new Uint8Array([255, 0, 0, 0, 255, 0, 0, 0, 255]); // RGB
* validatePalette(palette1); // OK - 3 colors
*
* const palette2 = new Uint8Array([0, 0, 0]); // Single black color
* validatePalette(palette2); // OK - 1 color
*
* // These will throw errors
* const badPalette1 = new Uint8Array([255, 0]); // Incomplete RGB triplet
* validatePalette(badPalette1); // Error: not divisible by 3
*
* const badPalette2 = new Uint8Array([]); // Empty
* validatePalette(badPalette2); // Error: empty palette
* ```
*/
export declare function validatePalette(palette: Uint8Array): void;
/**
* Calculates the color table size field for GIF format.
*
* Determines the size field value needed to represent a given number of colors
* in a GIF color table. The size field represents the power of 2 needed to
* accommodate all colors (e.g., size 3 = 2^4 = 16 colors).
*
* @param colorCount - Number of colors in the palette (1-256)
* @returns Size field value (0-7) for GIF color table
* @throws {GifValidationError} When color count is invalid
*
* @example
* ```typescript
* // Calculate required size fields
* calculateColorTableSize(1); // Returns 0 (2^1 = 2 colors minimum)
* calculateColorTableSize(4); // Returns 1 (2^2 = 4 colors)
* calculateColorTableSize(16); // Returns 3 (2^4 = 16 colors)
* calculateColorTableSize(128); // Returns 6 (2^7 = 128 colors)
* calculateColorTableSize(256); // Returns 7 (2^8 = 256 colors)
*
* // Edge cases
* calculateColorTableSize(0); // Error: invalid count
* calculateColorTableSize(300); // Error: exceeds maximum
* ```
*/
export declare function calculateColorTableSize(colorCount: number): number;
/**
* Pads a color table to the required size for GIF format.
*
* Extends a palette to match the required power-of-2 size by filling
* remaining slots with black (0,0,0) colors. This ensures the palette
* meets GIF format requirements.
*
* @param palette - Original palette data as RGB triplets
* @param requiredSize - Required size field (determines final palette size)
* @returns Padded palette with power-of-2 number of colors
*
* @example
* ```typescript
* // Original palette with 3 colors (9 bytes)
* const original = new Uint8Array([
* 255, 0, 0, // Red
* 0, 255, 0, // Green
* Pads color table to the required size
*/
export declare function padColorTable(palette: Uint8Array, requiredSize: number): Uint8Array;
/**
* Writes a 16-bit integer in little-endian format.
*
* Converts a 16-bit integer to a 2-byte Uint8Array in little-endian order
* (least significant byte first). This is the byte order used in GIF files.
*
* @param value - Integer value to encode (0-65535)
* @returns 2-byte array in little-endian format
* @throws {GifValidationError} When value is out of 16-bit range
*
* @example
* ```typescript
* // Convert various values
* write16BitLE(0); // Returns [0, 0]
* write16BitLE(255); // Returns [255, 0]
* write16BitLE(256); // Returns [0, 1]
* write16BitLE(65535); // Returns [255, 255]
*
* // Use in GIF writing
* const width = 320;
* const height = 240;
* const widthBytes = write16BitLE(width); // [64, 1]
* const heightBytes = write16BitLE(height); // [240, 0]
*
* // Error cases
* write16BitLE(-1); // Error: negative value
* write16BitLE(70000); // Error: exceeds 16-bit range
* ```
*/
export declare function write16BitLE(value: number): Uint8Array;
/**
* Clamps a numeric value between minimum and maximum bounds.
*
* Ensures a value stays within specified limits by constraining it to
* the given range. Values below minimum return minimum, values above
* maximum return maximum.
*
* @param value - Value to clamp
* @param min - Minimum allowed value
* @param max - Maximum allowed value
* @returns Value constrained to [min, max] range
*
* @example
* ```typescript
* // Basic clamping
* clamp(5, 0, 10); // Returns 5 (within range)
* clamp(-5, 0, 10); // Returns 0 (below minimum)
* clamp(15, 0, 10); // Returns 10 (above maximum)
*
* // Color component clamping
* clamp(300, 0, 255); // Returns 255 (valid color)
* clamp(-50, 0, 255); // Returns 0 (valid color)
*
* // Percentage clamping
* clamp(1.5, 0, 1); // Returns 1.0 (valid percentage)
* clamp(-0.2, 0, 1); // Returns 0.0 (valid percentage)
* ```
*/
export declare function clamp(value: number, min: number, max: number): number;
/**
* Converts milliseconds to GIF delay units.
*
* Transforms time in milliseconds to GIF delay format, which uses
* centiseconds (1/100th of a second). Minimum delay is 0.
*
* @param ms - Time in milliseconds
* @returns Delay in GIF units (centiseconds)
*
* @example
* ```typescript
* // Common frame rates
* msToGifDelay(100); // Returns 10 (100ms = 10 centiseconds)
* msToGifDelay(50); // Returns 5 (50ms = 5 centiseconds)
* msToGifDelay(1000); // Returns 100 (1000ms = 1 second = 100 centiseconds)
*
* // Animation examples
* const fastAnimation = msToGifDelay(33); // ~30 FPS
* const slowAnimation = msToGifDelay(500); // 2 FPS
* const veryFast = msToGifDelay(16); // ~60 FPS
*
* // Edge cases
* msToGifDelay(0); // Returns 0 (no delay)
* msToGifDelay(-100); // Returns 0 (negative clamped to 0)
* ```
*/
export declare function msToGifDelay(ms: number): number;
/**
* Converts GIF delay units to milliseconds.
*
* Transforms GIF delay format (centiseconds) back to milliseconds.
* This is the inverse operation of msToGifDelay().
*
* @param delay - Delay in GIF units (centiseconds)
* @returns Time in milliseconds
*
* @example
* ```typescript
* // Convert GIF delays back to milliseconds
* gifDelayToMs(10); // Returns 100 (10 centiseconds = 100ms)
* gifDelayToMs(5); // Returns 50 (5 centiseconds = 50ms)
* gifDelayToMs(100); // Returns 1000 (100 centiseconds = 1 second)
*
* // Round-trip conversion
* const originalMs = 250;
* const gifDelay = msToGifDelay(originalMs); // 25
* const convertedBack = gifDelayToMs(gifDelay); // 250
*
* // Use for timing calculations
* const frameDelay = 8; // From GIF file
* const frameRate = 1000 / gifDelayToMs(frameDelay); // Calculate FPS
* ```
*/
export declare function gifDelayToMs(delay: number): number;
/**
* Creates a validated RGB color object.
*
* Constructs an RGB color object with automatic validation of all components.
* This is a convenience function that combines color creation with validation.
*
* @param red - Red component (0-255)
* @param green - Green component (0-255)
* @param blue - Blue component (0-255)
* @returns Validated RGB color object
* @throws {GifValidationError} When any color component is invalid
*
* @example
* ```typescript
* // Create valid colors
* const red = createColor(255, 0, 0);
* const green = createColor(0, 255, 0);
* const blue = createColor(0, 0, 255);
* const white = createColor(255, 255, 255);
* const black = createColor(0, 0, 0);
*
* // Create custom colors
* const orange = createColor(255, 165, 0);
* const purple = createColor(128, 0, 128);
* const gray = createColor(128, 128, 128);
*
* // These will throw validation errors
* createColor(-1, 128, 255); // Invalid red
* createColor(255, 256, 0); // Invalid green
* createColor(128.5, 0, 255); // Non-integer red
* ```
*/
export declare function createColor(red: ColorIntensity, green: ColorIntensity, blue: ColorIntensity): RGBColor;
/**
* Calculates squared Euclidean distance between two RGB colors.
*
* Computes the squared distance in RGB color space without taking the square root,
* which is sufficient for color comparison and is more computationally efficient.
* Used in color quantization and nearest-color algorithms.
*
* @param a - First RGB color
* @param b - Second RGB color
* @returns Squared distance between colors (0-195075)
*
* @example
* ```typescript
* const red = { red: 255, green: 0, blue: 0 };
* const blue = { red: 0, green: 0, blue: 255 };
* const darkRed = { red: 128, green: 0, blue: 0 };
*
* // Calculate color distances
* const redBlueDistance = colorDistanceSquared(red, blue); // 130050
* const redDarkRedDistance = colorDistanceSquared(red, darkRed); // 16129
*
* // Find closest color
* const targetColor = { red: 200, green: 50, blue: 100 };
* const colors = [red, blue, darkRed];
*
* let closestColor = colors[0];
* let minDistance = colorDistanceSquared(targetColor, colors[0]);
*
* for (const color of colors) {
* const distance = colorDistanceSquared(targetColor, color);
* if (distance < minDistance) {
* minDistance = distance;
* closestColor = color;
* }
* }
* ```
*/
export declare function colorDistanceSquared(a: RGBColor, b: RGBColor): number;
/**
* Finds the index of the closest color in a palette.
*
* Searches through a palette of RGB colors to find the one with minimum
* Euclidean distance to the target color. Uses squared distance for efficiency.
*
* @param target - Target RGB color to match
* @param palette - Array of RGB colors to search
* @returns Index of the closest color in the palette
* @throws {GifValidationError} When palette is empty
*
* @example
* ```typescript
* const palette = [
* { red: 255, green: 0, blue: 0 }, // Index 0: Red
* { red: 0, green: 255, blue: 0 }, // Index 1: Green
* { red: 0, green: 0, blue: 255 }, // Index 2: Blue
* { red: 255, green: 255, blue: 0 } // Index 3: Yellow
* ];
*
* // Find closest matches
* const orange = { red: 255, green: 128, blue: 0 };
* const closestToOrange = findClosestColorIndex(orange, palette); // 0 (red)
*
* const lightBlue = { red: 100, green: 100, blue: 200 };
* const closestToLightBlue = findClosestColorIndex(lightBlue, palette); // 2 (blue)
*
* // Use for color quantization
* const targetColor = { red: 200, green: 200, blue: 50 };
* const paletteIndex = findClosestColorIndex(targetColor, palette);
* const quantizedColor = palette[paletteIndex];
* ```
*/
export declare function findClosestColorIndex(target: RGBColor, palette: RGBColor[]): number;
/**
* Converts raw palette data to an array of RGB color objects.
*
* Transforms a Uint8Array containing RGB triplets into an array of structured
* RGB color objects. This makes palette data easier to work with programmatically.
*
* @param palette - Raw palette data as RGB triplets (3 bytes per color)
* @returns Array of RGB color objects
* @throws {GifValidationError} When palette length is not divisible by 3
*
* @example
* ```typescript
* // Convert raw palette data
* const rawPalette = new Uint8Array([
* 255, 0, 0, // Red
* 0, 255, 0, // Green
* 0, 0, 255, // Blue
* 255, 255, 0 // Yellow
* ]);
*
* const colors = paletteToColors(rawPalette);
* // Result: [
* // { red: 255, green: 0, blue: 0 },
* // { red: 0, green: 255, blue: 0 },
* // { red: 0, green: 0, blue: 255 },
* // { red: 255, green: 255, blue: 0 }
* // ]
*
* // Use converted colors
* for (const color of colors) {
* console.log(`RGB(${color.red}, ${color.green}, ${color.blue})`);
* }
* ```
*/
export declare function paletteToColors(palette: Uint8Array): RGBColor[];
/**
* Converts an array of RGB color objects to raw palette data.
*
* Transforms structured RGB color objects into a flat Uint8Array of RGB triplets
* suitable for use in GIF files. This is the inverse of paletteToColors().
*
* @param colors - Array of RGB color objects
* @returns Raw palette data as RGB triplets
*
* @example
* ```typescript
* // Convert color objects to raw palette
* const colors = [
* { red: 255, green: 0, blue: 0 }, // Red
* { red: 0, green: 255, blue: 0 }, // Green
* { red: 0, green: 0, blue: 255 }, // Blue
* { red: 255, green: 255, blue: 0 } // Yellow
* ];
*
* const rawPalette = colorsToPalette(colors);
* // Result: Uint8Array([255, 0, 0, 0, 255, 0, 0, 0, 255, 255, 255, 0])
*
* // Round-trip conversion
* const originalRaw = new Uint8Array([128, 64, 192, 255, 128, 0]);
* const converted = paletteToColors(originalRaw);
* const backToRaw = colorsToPalette(converted);
* // backToRaw should equal originalRaw
*
* // Use in GIF creation
* const customPalette = colorsToPalette([
* { red: 34, green: 139, blue: 34 }, // Forest green
* { red: 255, green: 215, blue: 0 }, // Gold
* { red: 220, green: 20, blue: 60 } // Crimson
* ]);
* ```
*/
export declare function colorsToPalette(colors: RGBColor[]): Uint8Array;