npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

cross-image

v0.4.1

Published

A pure JavaScript, dependency-free, cross-runtime image processing library for Deno, Node.js, and Bun.

Readme

@cross/image

A pure JavaScript, dependency-free, cross-runtime image processing library for Deno, Node.js, and Bun. Decode, encode, manipulate, and process images in multiple formats including PNG, JPEG, WebP, GIF, and more—all without native dependencies.

📚 Full Documentation

Features

  • 🚀 Pure JavaScript - No native dependencies
  • 🔌 Pluggable formats - Easy to extend with custom formats
  • 📦 Cross-runtime - Works on Deno, Node.js (18+), and Bun
  • 🎨 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

Installation

Deno

import { Image } from "jsr:@cross/image";

Node.js

npm install cross-image
import { Image } from "cross-image";

Bun

npm install cross-image
import { Image } from "cross-image";

Quick Start

Deno

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);

Node.js

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);

Bun

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);

Supported Formats

| 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 | ⚠️ Baseline & Progressive | Pure-JS baseline & progressive DCT: decode with spectral selection; encode with 2-scan (DC+AC) | | WebP | ⚠️ Lossless | Pure-JS lossless VP8L | | TIFF | ⚠️ Basic | 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.

Advanced Usage

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”:

  • https://cross-image.56k.guru/api/

JPEG Tolerant Decoding

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

CMYK Color Space Support

The library provides utilities for working with CMYK (Cyan, Magenta, Yellow, Key/Black) color space, commonly used in professional printing and color manipulation.

Color Conversion Utilities

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 }

Image-Level CMYK Operations

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"));

Batch Conversion

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

CMYK TIFF Support

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

Base64 / Data URLs

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.

Fault-Tolerant Decoding for Other Formats

In addition to JPEG, @cross/image provides fault-tolerant decoding for several other formats that commonly encounter corruption or complex encoding patterns:

GIF Fault-Tolerant Decoding

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);
  },
});

WebP Fault-Tolerant Decoding (VP8L Lossless)

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;

When to Use Fault-Tolerant Modes

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

Warning Callbacks

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 warning
  • details: Optional additional context (usually the error object)

Metadata Support

@cross/image provides comprehensive EXIF 3.0 compliant metadata support for image files, including camera information, GPS coordinates, and InteropIFD compatibility markers.

Supported Metadata Fields

Basic Metadata:

  • title, description, author, copyright
  • creationDate - Date/time image was created

Camera Settings (JPEG, TIFF, WebP via XMP):

  • cameraMake, cameraModel - Camera manufacturer and model
  • lensMake, lensModel - Lens information
  • iso - ISO speed rating
  • exposureTime - Shutter speed in seconds
  • fNumber - Aperture (f-number)
  • focalLength - Focal length in mm
  • flash, whiteBalance - Capture settings
  • orientation - Image orientation (1=normal, 3=180°, 6=90°CW, 8=90°CCW)
  • software - Software used
  • userComment - 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

EXIF 3.0 Specification Compliance

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.)

Example Usage

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 }

Extracting Metadata Without Decoding

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 undefined for unsupported formats
  • Works with JPEG, PNG, WebP, TIFF, HEIC, and AVIF formats

Format-Specific Support

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)

Documentation

Development

deno task precommit

CI validates cross-runtime compatibility (Deno, Bun, Node). See CONTRIBUTING.md for contributor workflow details.

License

MIT License - see LICENSE file for details.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.