A pure JavaScript, dependency-free, cross-runtime image processing library for Deno, Node.js, Bun and browsers. Decode, encode, manipulate, and process images in multiple formats including PNG, JPEG, WebP, GIF, and more—all without native dependencies.
- 🚀 Pure JavaScript - No native dependencies
- 🔌 Pluggable formats - Easy to extend with custom formats
- 📦 Cross-runtime - Works on Deno, Node.js (18+), Bun and Browsers.
- 🎨 Multiple formats - PNG, APNG, JPEG, WebP, GIF, TIFF, BMP, ICO, DNG, PAM, PPM, PCX, ASCII, HEIC, and AVIF support
- ✂️ Image manipulation - Resize, crop, composite, and more
- 🎛️ Image processing - Chainable filters including
brightness,contrast,saturation,hue,exposure,blur,sharpen,sepia, and more - 🖌️ Drawing operations - Create, fill, and manipulate pixels
- 🧩 Multi-frame - Decode/encode animated GIFs, APNGs and multi-page TIFFs
- 🔧 Simple API - Easy to use, intuitive interface
import { Image } from "jsr:@cross/image";npm install cross-imageimport { Image } from "cross-image";npm install cross-imageimport { Image } from "cross-image";import { Image } from "jsr:@cross/image";
// Decode an image (auto-detects format)
const data = await Deno.readFile("input.png");
const image = await Image.decode(data);
console.log(`Image size: ${image.width}x${image.height}`);
// Create a new blank image
const canvas = Image.create(800, 600, 255, 255, 255); // white background
// Composite the loaded image on top
canvas.composite(image, 50, 50);
// Apply image processing filters
canvas
.brightness(0.1)
.contrast(0.2)
.saturation(-0.1)
.blur(1)
.sharpen(0.3);
// Encode in a different format
const jpeg = await canvas.encode("jpeg");
await Deno.writeFile("output.jpg", jpeg);import { Image } from "cross-image";
import { readFile, writeFile } from "node:fs/promises";
// Read an image (auto-detects format)
const data = await readFile("input.png");
const image = await Image.decode(data);
console.log(`Image size: ${image.width}x${image.height}`);
// Resize the image
image.resize({ width: 800, height: 600 });
// Save in a different format
const jpeg = await image.encode("jpeg");
await writeFile("output.jpg", jpeg);import { Image } from "cross-image";
// Read an image (auto-detects format)
const data = await Bun.file("input.png").arrayBuffer();
const image = await Image.decode(new Uint8Array(data));
console.log(`Image size: ${image.width}x${image.height}`);
// Resize the image and save
image.resize({ width: 800, height: 600 });
const jpeg = await image.encode("jpeg");
await Bun.write("output.jpg", jpeg);| Format | Pure-JS | Notes |
|---|---|---|
| PNG | ✅ Full | Complete pure-JS implementation |
| APNG | ✅ Full | Animated PNG with multi-frame |
| BMP | ✅ Full | Complete pure-JS implementation |
| ICO | ✅ Full | Windows Icon format |
| GIF | ✅ Full | Animated GIF with multi-frame |
| DNG | ✅ Full | Linear DNG (Uncompressed RGBA) |
| PAM | ✅ Full | Netpbm PAM format |
| PPM | ✅ Full | Netpbm PPM format (P3/P6) |
| PCX | ✅ Full | ZSoft PCX (RLE compressed) |
| ASCII | ✅ Full | Text-based ASCII art |
| JPEG | Pure-JS baseline & progressive DCT: decode with spectral selection; encode with 2-scan (DC+AC) | |
| WebP | Pure-JS lossless VP8L | |
| TIFF | Pure-JS uncompressed, LZW, PackBits, & Deflate; grayscale & RGB/RGBA | |
| HEIC | 🔌 Runtime | Requires ImageDecoder/OffscreenCanvas API support |
| AVIF | 🔌 Runtime | Requires ImageDecoder/OffscreenCanvas API support |
See the full format support documentation for detailed compatibility information.
Most users should stick to the Image API (Image.decode, image.encode, etc.).
If you need to work with format handlers directly (e.g. new PNGFormat()) or register your own
ImageFormat implementation via Image.registerFormat(...), see the API reference section
“Exported Format Classes”:
The JPEG decoder includes a tolerant decoding mode (enabled by default) that gracefully handles partially corrupted images or complex encoding patterns from mobile phone cameras. When enabled, the decoder will continue processing even if some blocks fail to decode, filling failed blocks with neutral values.
Features:
- Enabled by default - Handles real-world JPEGs from various devices
- Progressive JPEG support - Decodes both baseline and progressive JPEGs
- Configurable - Can be disabled for strict validation
- Fault-tolerant - Recovers partial image data instead of failing completely
- Zero configuration - Works automatically with the standard
Image.decode()API
When to use:
- Mobile phone JPEGs with complex encoding patterns
- Progressive JPEG images from web sources
- Images from various camera manufacturers
- Partially corrupted JPEG files
- Production applications requiring maximum compatibility
Example:
import { Image } from "jsr:@cross/image";
const data = await Deno.readFile("mobile-photo.jpg");
// Default behavior - tolerant decoding enabled
const image = await Image.decode(data);
// Strict mode - fail fast on decode errors
const strictImage = await Image.decode(data, { tolerantDecoding: false });
// Optional: receive warnings during partial decode (pure-JS decoder paths)
const imageWithWarnings = await Image.decode(data, {
tolerantDecoding: true,
runtimeDecoding: "never",
onWarning: (message, details) => {
console.log(`JPEG Warning: ${message}`, details);
},
});Note: When using Image.decode(), the library automatically tries runtime-optimized decoders
The library provides utilities for working with CMYK (Cyan, Magenta, Yellow, Key/Black) color space, commonly used in professional printing and color manipulation.
import { cmykToRgb, rgbToCmyk } from "jsr:@cross/image";
// Convert RGB to CMYK
const [c, m, y, k] = rgbToCmyk(255, 0, 0); // Red
console.log({ c, m, y, k }); // { c: 0, m: 1, y: 1, k: 0 }
// Convert CMYK back to RGB
const [r, g, b] = cmykToRgb(c, m, y, k);
console.log({ r, g, b }); // { r: 255, g: 0, b: 0 }import { Image } from "jsr:@cross/image";
// Load an image and convert to CMYK
const data = await Deno.readFile("photo.jpg");
const image = await Image.decode(data);
// Get CMYK representation (Float32Array with 4 values per pixel)
const cmykData = image.toCMYK();
// Create an image from CMYK data
const restored = Image.fromCMYK(cmykData, image.width, image.height);
// Save the restored image
await Deno.writeFile("output.png", await restored.encode("png"));import { cmykToRgba, rgbaToCmyk } from "jsr:@cross/image";
// Convert entire image data to CMYK
const rgbaData = new Uint8Array([255, 0, 0, 255]); // Red pixel
const cmykData = rgbaToCmyk(rgbaData);
// Convert CMYK data back to RGBA
const rgbaRestored = cmykToRgba(cmykData);Use Cases:
- Pre-press and print preparation workflows
- Color space conversion for professional printing
- Color analysis and manipulation in CMYK space
- Educational tools for understanding color models
TIFF format has native support for CMYK images:
import { Image } from "jsr:@cross/image";
// Decode CMYK TIFF (automatically converted to RGBA)
const cmykTiff = await Deno.readFile("cmyk-image.tif");
const image = await Image.decode(cmykTiff); // Automatic CMYK → RGBA conversion
// Encode image as CMYK TIFF
const output = await image.encode("tiff", { cmyk: true });
await Deno.writeFile("output-cmyk.tif", output);
// CMYK works with all TIFF compression methods
const compressed = await image.encode("tiff", {
cmyk: true,
compression: "lzw", // or "packbits", "deflate", "none"
});Benefits:
- Seamless workflow: CMYK TIFFs decode automatically, no special handling needed
- Print-ready output: Generate CMYK TIFFs for professional printing workflows
- Full compression support: CMYK works with all TIFF compression methods
- Industry standard: TIFF is the preferred format for CMYK images in print production
The library includes small utilities for working with base64 and data: URLs.
import { Image, parseDataUrl, toDataUrl } from "jsr:@cross/image";
const image = Image.create(2, 2, 255, 0, 0);
const pngBytes = await image.encode("png");
const dataUrl = toDataUrl("image/png", pngBytes);
const parsed = parseDataUrl(dataUrl);
// parsed.bytes is a Uint8Array containing the PNG
const roundtrip = await Image.decode(parsed.bytes, "png");
console.log(roundtrip.width, roundtrip.height);(ImageDecoder API) first, falling back to the pure JS decoder with tolerant mode for maximum compatibility.
In addition to JPEG, @cross/image provides fault-tolerant decoding for several other formats that commonly encounter corruption or complex encoding patterns:
The GIF decoder supports frame-level tolerance for animated GIFs. When enabled (default), corrupted frames are skipped instead of causing complete decode failure.
Features:
- Enabled by default - Handles multi-frame GIFs with some corrupted frames
- Frame-level recovery - Skips bad frames, preserves good ones
- LZW decompression errors - Continues past compression errors
Example:
import { Image } from "jsr:@cross/image";
const data = await Deno.readFile("animated.gif");
// Tolerant mode (default) - skips corrupted frames
const tolerantFrames = await Image.decodeFrames(data);
// Strict mode - throws on first corrupted frame
const strictFrames = await Image.decodeFrames(data, {
tolerantDecoding: false,
});
// Optional: receive warnings when frames are skipped
const framesWithWarnings = await Image.decodeFrames(data, {
tolerantDecoding: true,
runtimeDecoding: "never",
onWarning: (message, details) => {
console.log(`GIF Warning: ${message}`, details);
},
});The WebP VP8L (lossless) decoder supports pixel-level tolerance. When enabled (default), decoding errors result in gray pixels for remaining data instead of complete failure.
Features:
- Enabled by default - Handles VP8L images with Huffman/LZ77 errors
- Pixel-level recovery - Fills remaining pixels with neutral gray
- Huffman decode errors - Continues past invalid codes
Example:
import { Image } from "jsr:@cross/image";
const data = await Deno.readFile("image.webp");
// Tolerant mode (default) - fills bad pixels with gray
const tolerantImage = await Image.decode(data);
// Strict mode - throws on first decode error
const strictImage = await Image.decode(data, { tolerantDecoding: false });
// Optional: receive warnings during partial decode
const imageWithWarnings = await Image.decode(data, {
tolerantDecoding: true,
runtimeDecoding: "never",
onWarning: (message, details) => {
console.log(`WebP Warning: ${message}`, details);
},
});
void imageWithWarnings;Use tolerant decoding (default) when:
- Processing user-uploaded images from various sources
- Building production applications requiring maximum compatibility
- Handling images from mobile devices or cameras
- Recovering data from partially corrupted files
- Batch processing where some failures are acceptable
Use strict decoding when:
- Validating image file integrity
- Quality control in professional workflows
- Detecting file corruption explicitly
- Testing image encoder implementations
Decoding APIs accept an optional onWarning callback that gets invoked when non-fatal issues occur
during decoding. This is useful for logging, monitoring, or debugging decoding issues without using
console methods.
Example:
import { Image } from "jsr:@cross/image";
const data = await Deno.readFile("input.webp");
const image = await Image.decode(data, {
tolerantDecoding: true,
runtimeDecoding: "never",
onWarning: (message, details) => {
// Log to your preferred logging system
myLogger.warn(message, details);
},
});
void image;The callback receives:
message: A human-readable description of the warningdetails: Optional additional context (usually the error object)
@cross/image provides comprehensive EXIF 3.0 compliant metadata support for image files, including camera information, GPS coordinates, and InteropIFD compatibility markers.
Basic Metadata:
title,description,author,copyrightcreationDate- Date/time image was created
Camera Settings (JPEG, TIFF, WebP via XMP):
cameraMake,cameraModel- Camera manufacturer and modellensMake,lensModel- Lens informationiso- ISO speed ratingexposureTime- Shutter speed in secondsfNumber- Aperture (f-number)focalLength- Focal length in mmflash,whiteBalance- Capture settingsorientation- Image orientation (1=normal, 3=180°, 6=90°CW, 8=90°CCW)software- Software useduserComment- User notes
GPS Coordinates (All EXIF formats: JPEG, PNG, WebP, TIFF):
latitude,longitude- GPS coordinates in decimal degrees- Full microsecond precision with DMS (degrees-minutes-seconds) conversion
DPI (JPEG, PNG, TIFF):
dpiX,dpiY- Dots per inch for printing
The library implements the EXIF 3.0 specification with:
- 50+ Exif Sub-IFD tags for comprehensive camera metadata
- 30+ IFD0 tags for image information
- InteropIFD support for format compatibility (R98/sRGB, R03/Adobe RGB, THM/thumbnail)
- GPS IFD with proper coordinate conversion
- All EXIF data types (BYTE, ASCII, SHORT, LONG, RATIONAL, etc.)
import { Image } from "jsr:@cross/image";
// Load an image
const data = await Deno.readFile("photo.jpg");
const image = await Image.decode(data);
// Set metadata
image.setMetadata({
author: "Jane Photographer",
copyright: "© 2024",
cameraMake: "Canon",
cameraModel: "EOS R5",
iso: 800,
exposureTime: 0.004, // 1/250s
fNumber: 2.8,
focalLength: 50,
});
// Set GPS coordinates
image.setPosition(40.7128, -74.0060); // NYC
// Check what metadata a format supports
const jpegSupports = Image.getSupportedMetadata("jpeg");
console.log(jpegSupports); // Includes ISO, camera info, GPS, etc.
// Save with metadata
const jpeg = await image.encode("jpeg");
await Deno.writeFile("output.jpg", jpeg);
// Metadata is preserved on reload!
const loaded = await Image.decode(jpeg);
console.log(loaded.metadata?.cameraMake); // "Canon"
console.log(loaded.getPosition()); // { latitude: 40.7128, longitude: -74.0060 }For quickly reading metadata from images without the overhead of decoding pixel data, use
Image.extractMetadata(). This is particularly useful for:
- Reading EXIF data from large images or photos
- Extracting metadata from images with unsupported compression
- Building image catalogs or galleries
- Processing metadata in batch operations
import { Image } from "jsr:@cross/image";
// Extract metadata without decoding pixels
const data = await Deno.readFile("large-photo.jpg");
const metadata = await Image.extractMetadata(data);
console.log(metadata?.cameraMake); // "Canon"
console.log(metadata?.iso); // 800
console.log(metadata?.exposureTime); // 0.004
// Works with auto-detection
const metadata2 = await Image.extractMetadata(data); // Detects JPEG
// Or specify format explicitly
const metadata3 = await Image.extractMetadata(data, "jpeg");This method is significantly faster than full decode when you only need metadata, as it:
- Skips pixel data decompression
- Only parses metadata chunks/markers
- Returns
undefinedfor unsupported formats - Works with JPEG, PNG, WebP, TIFF, HEIC, and AVIF formats
Use Image.getSupportedMetadata(format) to check which fields are supported:
Image.getSupportedMetadata("jpeg"); // Full camera metadata + GPS (21 fields)
Image.getSupportedMetadata("tiff"); // Comprehensive EXIF + GPS + InteropIFD (23+ fields)
Image.getSupportedMetadata("png"); // DateTime, GPS, DPI, basic text (9 fields)
Image.getSupportedMetadata("webp"); // Enhanced XMP + GPS (15 fields - includes camera metadata!)
Image.getSupportedMetadata("heic"); // Full camera metadata + GPS (19 fields)
Image.getSupportedMetadata("avif"); // Full camera metadata + GPS (19 fields)Format Highlights:
- JPEG: Most comprehensive EXIF support, including all camera settings and GPS
- TIFF: Full EXIF 3.0 support with IFD structure, InteropIFD compatibility
- WebP: Enhanced XMP implementation with Dublin Core, EXIF, and TIFF namespaces
- PNG: Basic EXIF support via eXIf chunk plus GPS coordinates
- HEIC: Full EXIF metadata extraction including camera settings, GPS, and image info (runtime-dependent encoding)
- AVIF: Full EXIF metadata extraction including camera settings, GPS, and image info (runtime-dependent encoding)
- API Reference - Complete API documentation
- Format Support - Supported formats and specifications
- Image Processing - Filters, manipulation, and
color adjustments
- Filters - Blur, sharpen, and noise reduction
- Manipulation - Resize, crop, composite, and draw
- Color Adjustments - Brightness, contrast, saturation, and more
- Examples - Practical examples for common tasks
- Decoding & Encoding - Format-specific examples
- Using Filters - Filter workflows and techniques
- Manipulation - Resizing, cropping, and compositing
- Multi-Frame Images - Animated GIFs, APNGs, and TIFFs
- JPEG Implementation - Technical details for JPEG
- WebP Implementation - Technical details for WebP
- TIFF Implementation - Technical details for TIFF
- GIF Implementation - Technical details for GIF
deno task precommitCI validates cross-runtime compatibility (Deno, Bun, Node). See CONTRIBUTING.md for contributor workflow details.
MIT License - see LICENSE file for details.
Contributions are welcome! Please feel free to submit a Pull Request.