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 🙏

© 2026 – Pkg Stats / Ryan Hefner

@commoncase/scanline-image-transform

v0.0.1-experimental.0

Published

Bounded-memory JPEG and PNG to WebP transforms for Cloudflare Workers

Readme

Scanline Image Transform

CI License: MIT

Work in progress — an experimental, bounded-memory JPEG/PNG to WebP path for Cloudflare Workers.

Scanline Image Transform is a TypeScript and WebAssembly library maintained by Common Case. It decodes supported images incrementally and avoids allocating a complete decoded source bitmap, which makes it useful for exploring image processing inside the memory envelope of Cloudflare Workers.

The project is not published to npm yet. The API, supported-image matrix, memory behavior, and package boundaries may change while the implementation is being hardened.

What it does

  • Accepts a Uint8Array, ReadableStream<Uint8Array>, or async iterable as input.
  • Decodes baseline, single-scan JPEG and legal non-interlaced or Adam7 PNG inputs.
  • Resizes with contain or cover semantics using nearest-neighbor sampling.
  • Emits WebP, including alpha when the PNG source has transparency.
  • Exposes transform metadata, conservative memory diagnostics, stable error codes, and AbortSignal cancellation.
  • Includes a small Fetch-compatible Worker adapter for POST image transforms.

The current pipeline is:

JPEG or PNG input stream
  -> bounded scanline/pass decoder in WASM
  -> resize into destination-sized YUV420(A) planes
  -> stock libwebp encoder in WASM
  -> WebP ReadableStream

The input side is bounded, but the current WebP encoder still receives a complete resized destination picture. The public result is therefore currently one output chunk. A row-fed encoder is a future architecture slice, not a promise of this WIP release.

Current status

The current branch contains a working feasibility implementation and a v1 contract, not a stable production release. The main open architectural limitation is output-side memory: destination planes and stock libwebp encoder working memory scale with the requested output size.

The exact behavior is documented in the v1 contract. The repository also contains architecture decisions, benchmark reports, and input-variant plans.

Use from a checkout

The package is not published yet. The generated dist/ package boundary is checked in so other repositories can consume an immutable Git revision during development without a sibling workspace or a build-time native codec toolchain. Use a local checkout when developing the package itself.

Prerequisites:

  • Bun 1.3.14 or compatible.
  • Emscripten with emcc available; CI uses 3.1.74.
  • cjpeg and JPEG development headers for the optional native probe (bun run test:native).
git clone https://github.com/commoncase/scanline-image-transform.git
cd scanline-image-transform
bun install

Useful checks:

bun run typecheck       # TypeScript typecheck
bun run test            # Build WASM and run the test suite
bun run build:package   # Build dist/ for package consumers
bun pm pack --dry-run   # Inspect the package boundary

The stress, native, and benchmark commands are available when the corresponding local toolchain is installed:

bun run test:stress
bun run test:native
bun run benchmark:memory

Core API

The core API accepts a streamed request body and returns a WebP stream:

import { transformImage } from "@commoncase/scanline-image-transform";

export async function resizeImage(request: Request): Promise<Response> {
  if (!request.body) {
    return new Response("Image body required", { status: 400 });
  }

  const result = await transformImage({
    input: request.body,
    outputFormat: "webp",
    resize: {
      width: 1200,
      height: 1200,
      fit: "contain",
    },
    quality: 82,
    signal: request.signal,
  });

  return new Response(result.stream, {
    headers: { "content-type": "image/webp" },
  });
}

result.metadata reports the input and output dimensions and formats. result.diagnostics reports input buffering, decoder and encoder WASM high-water marks, destination memory, and the conservative preEncoderPeakBytes accounting used by the benchmarks.

Worker adapter

For a small Cloudflare Worker endpoint, use the Fetch-compatible adapter:

import { createImageWorkerHandler } from "@commoncase/scanline-image-transform";

export default {
  fetch: createImageWorkerHandler(),
};

The adapter accepts POST requests with an image/jpeg or image/png body and these query parameters:

/transform?width=1200&height=1200&fit=contain&quality=82

To try the repository's probe locally:

bun run dev
curl -X POST \
  -H 'content-type: image/jpeg' \
  --data-binary @input.jpg \
  'http://localhost:8787/transform?width=1200&height=1200&fit=contain' \
  --output output.webp

The probe Worker is for development and validation. Deploying it is optional:

bunx wrangler login
bun run deploy:dry
bun run deploy

Supported inputs and output

| Format | Current support | | --- | --- | | JPEG | 8-bit baseline, single-scan JPEG; EXIF orientations 1–8 are visually normalized | | PNG | Legal 1/2/4/8/16-bit grayscale, RGB, indexed, grayscale+alpha, and RGBA; non-interlaced and Adam7 interlaced | | Output | WebP only; PNG transparency is preserved as WebP alpha |

Progressive and multi-scan JPEG, unsupported PNG color/bit-depth combinations, and other image formats are rejected deliberately. Source EXIF, ICC, XMP, and comment metadata are not copied to the WebP result; recognized source color metadata is normalized to the sRGB output path.

Default limits

Limits can be overridden through transformImage options or Worker adapter options. They exist to make resource use explicit at the call boundary.

| Limit | Default | | --- | ---: | | Compressed input bytes | 50 MiB | | Source width or height | 8,192 pixels | | Source pixels | 50,000,000 | | Requested output width or height | 1,920 pixels | | Requested output pixels | 3,686,400 | | WebP quality | 82 (range 0–100) |

contain fits the source inside the requested rectangle and may produce a smaller output to preserve its aspect ratio. cover fills the requested dimensions and center-crops when the aspect ratios differ.

Worker errors use these stable status mappings:

| Condition | Status | | --- | ---: | | Invalid transform parameters | 400 | | Input or output limit exceeded | 413 | | Unsupported media or image format | 415 | | Malformed supported image | 422 |

Memory model

The decoders retain a bounded input window, one normalized source/pass row, and the previous PNG filter row. They do not allocate a complete source pixel bitmap. Adam7 PNG and oriented JPEG use output-sized scatter paths instead of reconstructing a full source frame.

The current encoder receives destination-sized YUV420 planes, plus a destination alpha plane for transparent PNG input. preEncoderPeakBytes is conservative local accounting that includes decoder memory, pending input, destination storage, and WebP WASM linear-memory high-water; it is not a direct Cloudflare per-request RSS measurement. See the output-memory benchmark for the measured trade-off.

Documentation

Before a stable release

  • Decide the stable package version and publish boundary.
  • Continue validating memory behavior on the target Workers runtime.
  • Decide whether a row-fed WebP encoder is needed to make output memory independent of destination size.
  • Keep progressive/multi-scan JPEG rejected unless a decoder meets the source-memory gate.

License

MIT