JSPM

  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 10
  • Score
    100M100P100Q78951F
  • License MIT

High-performance image resize for browsers and Node.js. Rust/WASM accelerated, zero dependencies, pure JS fallback.

Package Exports

  • quickpix
  • quickpix/decode
  • quickpix/easy
  • quickpix/encode
  • quickpix/fallback
  • quickpix/metadata
  • quickpix/pipeline-worker
  • quickpix/pipeline-worker.js
  • quickpix/resize-worker
  • quickpix/resize-worker.js
  • quickpix/utils

Readme

quickpix

npm version npm bundle size zero dependencies license

한국어

High-performance image resize for browsers and Node.js. Rust/WASM accelerated with pure JS fallback. Zero dependencies.

npm install quickpix

Quick Start — High-Level API (QuickPixEasy)

For most use cases, QuickPixEasy is all you need. Blob/File in → resize → Blob out, in a single call.

import { QuickPixEasy } from "quickpix";

const qp = new QuickPixEasy({
  filter: "lanczos",           // resize filter (default: bilinear)
  outputMimeType: "image/jpeg",
  outputQuality: 0.85,
  preserveMetadata: true,      // preserve EXIF/ICC/IPTC metadata
  autoRotate: true,            // auto-correct EXIF orientation (default: true)
});

Resize Blob/File

const input = document.querySelector('input[type="file"]');
const file = input.files[0];

// Exact dimensions
const resized = await qp.resizeBlob(file, 1200, 800);

// Width only — height auto-calculated (aspect ratio preserved)
const resized2 = await qp.resizeBlob(file, 1200, null);

// Height only — width auto-calculated
const resized3 = await qp.resizeBlob(file, null, 800);

// Max dimension — longest side fits within limit
const resized4 = await qp.resizeBlob(file, null, null, { maxDimension: 4096 });

// resizeFile is an alias for resizeBlob
const resized5 = await qp.resizeFile(file, 1200, null);

Create Thumbnails

Automatically preserves aspect ratio. The longest side fits within maxDimension.

// 6000x4000 image → 200x133 thumbnail
const thumbnail = await qp.createThumbnail(file, 200);

// Also accepts Canvas, ImageData, HTMLImageElement
const thumb2 = await qp.createThumbnail(canvasElement, 150);

Draw to Canvas

const canvas = document.getElementById("preview");
canvas.width = 800;
canvas.height = 600;

await qp.resizeToCanvas(file, canvas, { filter: "lanczos" });

Batch Parallel Processing

Processes multiple images concurrently using a Web Worker pool. Each image runs the full decode→resize→encode pipeline in a separate worker.

const results = await qp.batchResize([
  { source: photo1, maxDimension: 800 },
  { source: photo2, width: 600, height: 400 },
  { source: photo3, maxDimension: 200 },
]);
// results = [Blob, Blob, Blob]

Metadata Preservation

By default, Canvas API strips all EXIF, ICC profiles, and other metadata. Set preserveMetadata: true to re-inject the original JPEG metadata into the output.

// Preserve metadata (EXIF date, GPS, camera info, ICC color profile, etc.)
const withMeta = await qp.resizeBlob(photo, 1200, 800, {
  preserveMetadata: true,
  outputMimeType: "image/jpeg",
});

// Strip metadata (default — better for privacy)
const stripped = await qp.resizeBlob(photo, 1200, 800, {
  preserveMetadata: false,
});

EXIF Orientation Auto-Correction

Smartphone photos store rotation info in EXIF. With autoRotate: true (default), images are automatically corrected to the right orientation.

// autoRotate: true (default) — portrait photos display correctly
const rotated = await qp.resizeBlob(phonePhoto, 800, 600);

// autoRotate: false — keep original pixel orientation
const raw = await qp.resizeBlob(phonePhoto, 800, 600, { autoRotate: false });

Fit Modes

// contain (default): fit within 800x600, preserve aspect ratio
const a = await qp.resizeBlob(photo, 800, 600, { fit: "contain" });

// cover: fill 800x600 completely, preserve aspect ratio (may crop)
const b = await qp.resizeBlob(photo, 800, 600, { fit: "cover" });

// fill: stretch to exactly 800x600, ignore aspect ratio
const c = await qp.resizeBlob(photo, 800, 600, { fit: "fill" });

Cleanup

qp.destroy(); // terminate worker pool and release resources

Options Reference

Option Default Description
filter "bilinear" Resize filter (nearest, bilinear, box, hamming, lanczos)
maxWorkers navigator.hardwareConcurrency Max worker pool size
idleTimeout 30000 Auto-terminate idle workers (ms)
outputMimeType "image/png" Output image format
outputQuality 0.92 JPEG/WebP quality (0–1)
useWasm true Enable WASM acceleration
preserveMetadata false Preserve EXIF/ICC/IPTC metadata
autoRotate true Auto-correct EXIF orientation
workerURL null Pipeline worker URL (or data: URL) for bundlers that do not rewrite worker paths
requireWorker false If true, throws when worker pipeline is unavailable instead of falling back to main thread
wasmPath null Override WASM module URL

Low-Level API (QuickPix)

For direct RGBA buffer manipulation or fine-grained control.

import { QuickPix } from "quickpix";

const qp = new QuickPix({ useWasm: true, filter: "lanczos" });

const src = new Uint8ClampedArray(640 * 480 * 4);
const out = await qp.resizeBuffer(src, 640, 480, 320, 240, {
  filter: "lanczos",
});

console.log(out.width, out.height, out.data.length); // 320 240 307200

Bundler-safe worker setup (Next, Vite, Rollup, esbuild, webpack, Turbopack)

QuickPix resolves worker entry from multiple candidates by default, but for bundlers that do not rewrite worker URLs reliably, pass workerURL explicitly. If you need hard failure when worker mode is unavailable, set requireWorker: true.

Example config templates: examples/bundlers

import { QuickPix, QuickPixEasy } from "quickpix";
import resizeWorker from "quickpix/resize-worker.js?url";
import pipelineWorker from "quickpix/pipeline-worker.js?url";

const qp = new QuickPix({
  workerURL: resizeWorker,
  requireWorker: true,
});

const qpe = new QuickPixEasy({
  workerURL: pipelineWorker,
  requireWorker: true,
});

If your setup already handles worker modules, this also works:

import resizeWorker from "quickpix/resize-worker?url";
import pipelineWorker from "quickpix/pipeline-worker?url";

Rollup users can use either the ?url form above (with @rollup/plugin-url) or the plain worker file path:

import resizeWorker from "quickpix/resize-worker.js";
import pipelineWorker from "quickpix/pipeline-worker.js";

You can also pass a function factory to return a Worker instance directly:

import resizeWorkerURL from "quickpix/resize-worker.js?url";

const qp = new QuickPix({
  workerURL: [() => new Worker(resizeWorkerURL, { type: "module" })],
  requireWorker: true,
});

requireWorker: true gives behavior similar to image-blob-reduce: it throws when worker paths or runtime are unavailable instead of falling back to main-thread processing.

Stable workerURL combos by bundler

Copy this section as a reference and choose one option per stack.

1) Next.js (webpack / Turbopack)

import { QuickPix, QuickPixEasy } from "quickpix";
import resizeWorker from "quickpix/resize-worker.js?url";
import pipelineWorker from "quickpix/pipeline-worker.js?url";

const qp = new QuickPix({ workerURL: resizeWorker, requireWorker: true });
const qpe = new QuickPixEasy({ workerURL: pipelineWorker, requireWorker: true });

Alternative (if path rewriting is odd in your setup):

import { QuickPix } from "quickpix";

const qp = new QuickPix({
  workerURL: [() => import("quickpix/resize-worker.js?url").then((m) => new Worker(m.default, { type: "module" }))],
  requireWorker: true,
});

2) Vite

import { QuickPix, QuickPixEasy } from "quickpix";
import resizeWorker from "quickpix/resize-worker.js?worker";
import pipelineWorker from "quickpix/pipeline-worker.js?worker";

const qp = new QuickPix({ workerURL: resizeWorker, requireWorker: true });
const qpe = new QuickPixEasy({ workerURL: pipelineWorker, requireWorker: true });

Fallback when ?worker form is not enabled:

import resizeWorker from "quickpix/resize-worker.js?url";
import pipelineWorker from "quickpix/pipeline-worker.js?url";

3) Rollup

// rollup.config.js
import url from "@rollup/plugin-url";

export default {
  plugins: [
    url({
      include: [
        /node_modules\/quickpix\/.*(resize-worker|pipeline-worker)\.js$/,
      ],
      limit: 0,
      emitFiles: true,
      fileName: "[name][extname]",
    }),
  ],
};
import { QuickPix, QuickPixEasy } from "quickpix";
import resizeWorker from "quickpix/resize-worker.js";
import pipelineWorker from "quickpix/pipeline-worker.js";

const qp = new QuickPix({ workerURL: resizeWorker, requireWorker: true });
const qpe = new QuickPixEasy({ workerURL: pipelineWorker, requireWorker: true });

4) esbuild

// esbuild CLI
// Use explicit URL-form import in source. If needed, force worker scripts to file-url output.
esbuild src/index.js --bundle --platform=browser --outdir=dist \
  --loader:.js=file
import { QuickPix, QuickPixEasy } from "quickpix";
import resizeWorker from "quickpix/resize-worker.js?url";
import pipelineWorker from "quickpix/pipeline-worker.js?url";

const qp = new QuickPix({ workerURL: resizeWorker, requireWorker: true });
const qpe = new QuickPixEasy({ workerURL: pipelineWorker, requireWorker: true });

5) webpack

// webpack 5 config (asset-module default)
module.exports = {
  module: {
    rules: [
      {
        test: /quickpix\\/(.*)worker\\.js$/,
        type: "asset/resource",
      },
    ],
  },
};
import { QuickPix, QuickPixEasy } from "quickpix";
import resizeWorker from "quickpix/resize-worker.js?url";
import pipelineWorker from "quickpix/pipeline-worker.js?url";

const qp = new QuickPix({ workerURL: resizeWorker, requireWorker: true });
const qpe = new QuickPixEasy({ workerURL: pipelineWorker, requireWorker: true });

Supported Filters

Filter Description Speed Quality
nearest Nearest neighbor Fastest Low
bilinear 2x2 linear interpolation (default) Fast Medium
box Box average Medium Medium
hamming Hamming window Slow High
lanczos Lanczos3 sinc-based Slowest Best

Metadata Module

Standalone module for direct EXIF/ICC/IPTC manipulation.

import { readOrientation, extractSegments, injectSegments } from "quickpix/metadata";

const buffer = await file.arrayBuffer();
const orientation = readOrientation(buffer);
const segments = extractSegments(buffer);
const restored = await injectSegments(resizedJpegBlob, segments);

Install & Build

npm install quickpix

# Development (build from source)
npm install
npm run build:wasm    # Rust → WASM (requires wasm-pack)
npm run test:js
npm run test:rust

Benchmarks

npm run bench           # performance
npm run bench:compare   # vs pica
npm run bench:memory    # memory profiling
npm run bench:native    # vs sharp (libvips)
npm run bench:quality   # quality comparison

Browser Compatibility

Feature Chrome 69+ Firefox 105+ Safari 16.4+
Pipeline worker (optimal) Yes Yes Yes
JS fallback All browsers All browsers All browsers

Automatically falls back to main-thread processing when OffscreenCanvas is unavailable.

License

MIT