@winrisef/jpegli-wasm
v0.1.0
Published
Browser and Web Worker JPEG encoder powered by Google JPEGli WebAssembly
Maintainers
Readme
@winrisef/jpegli-wasm
A small ESM browser package that encodes sRGB ImageData with
JPEGli compiled to WebAssembly.
This is an independent, third-party wrapper. It is not an official Google product and is not endorsed or supported by Google.
The package contains the compiled .wasm; consumers do not need Docker,
CMake, or Emscripten. It does not use cjpegli, Emscripten FS, MozJPEG,
canvas encoding, @jsquash/jpeg, or a fallback encoder.
Install
npm install @winrisef/jpegli-wasmUsage
import { encode } from "@winrisef/jpegli-wasm";
const output = await encode(imageData, {
distance: 1.0,
chromaSubsampling: "420",
progressive: true,
});
const blob = new Blob([output], { type: "image/jpeg" });encode works in browsers and dedicated workers. With Next.js, call it from a
Client Component or a browser-only dynamic import. Vite can import it normally.
The first call fetches and instantiates jpegli.wasm; later calls reuse that
module instance.
For a worker, create a module worker with your bundler:
// main.ts
const worker = new Worker(new URL("./encoder.worker.ts", import.meta.url), {
type: "module",
});
worker.onmessage = ({ data }) => {
if (data.error) console.error(data.error);
else console.log(new Blob([data.output], { type: "image/jpeg" }));
};
worker.postMessage(imageData);
// encoder.worker.ts
import { encode } from "@winrisef/jpegli-wasm";
self.onmessage = async ({ data }) => {
try {
const output = await encode(data, { distance: 1, chromaSubsampling: "420" });
self.postMessage({ output }, { transfer: [output.buffer] });
} catch (error) {
self.postMessage({ error: String(error) });
}
};API
type ChromaSubsampling = "444" | "422" | "420" | "440";
interface ImageDataLike {
readonly data: Uint8Array | Uint8ClampedArray;
readonly width: number;
readonly height: number;
readonly colorSpace?: string;
}
interface EncodeOptions {
readonly distance?: number; // 0..25, default 1.0; lower is higher quality
readonly chromaSubsampling?: ChromaSubsampling; // default "444"
readonly progressive?: boolean; // default true
readonly adaptiveQuantization?: boolean; // default true
readonly optimizeCoding?: boolean; // default true
}
function encode(
imageData: ImageDataLike,
options?: EncodeOptions,
): Promise<Uint8Array<ArrayBuffer>>;The input must contain exactly width * height * 4 interleaved RGBA bytes.
Only sRGB input is supported in this first release; alpha is ignored by JPEG.
Every option maps to JPEGli's pinned API:
distancecallsjpegli_set_distance.chromaSubsamplingsets the JPEG component sampling factors used by JPEGli.progressiveselectsjpegli_set_progressive_level(2)or level0.adaptiveQuantizationcallsjpegli_enable_adaptive_quantization.optimizeCodingsets libjpeg-compatibleoptimize_coding.
JPEGli also optimizes Huffman tables in progressive mode, even when
optimizeCoding is false. To disable that optimization, set both
progressive: false and optimizeCoding: false.
distance: 0 does not mean lossless JPEG. The wrapper accepts 0..25; this is
the supported wrapper range, not a claim that upstream enforces these bounds.
The package requires a browser with WebAssembly SIMD and ES2022 support.
TypeScript consumers need TypeScript 5.7 or newer for typed ArrayBuffer views.
Encoding itself is synchronous on the calling thread after asynchronous WASM
initialization. Use a dedicated Worker for large images to keep the UI responsive.
Dimensions must be at most 65500 pixels per side. Memory limits may require much
smaller images. Alpha is discarded, not composited onto a background.
Failed WASM downloads can be retried by calling encode again.
Reproducible WASM build
- JPEGli commit:
031a0077f5799a6041004267fc12b956c1f52a20 - Emscripten:
4.0.10
docker build --target artifacts --output type=local,dest=. .All C/C++ and WebAssembly compilation happens in the pinned Emscripten Docker image. The image checks out the exact JPEGli commit and its exact submodule revisions. TypeScript compilation is the only native host build step.
Development verification
npm ci
npm run build
npm test
npm run test:package
npm run test:repro
npm pack --dry-runThe browser suite checks JPEG structure, distance-dependent output, progressive/sequential SOF markers, all exposed chroma sampling factors, repeated encoding, and a real dedicated worker.
test:package installs an actual tarball with normal npm lifecycle behavior,
type-checks the Blob example, and verifies browser and Worker encoding through
Vite and Next.js in development and production. It uses the pinned development
dependencies in this repository and keeps generated fixtures under .cache/.
test:repro compares two independent no-cache Docker builds and the working
WASM/JS artifacts by SHA-256. The Docker build uses linux/amd64 on every host.
Development requires Node.js 22.12+, Docker, and Chrome/Chromium (set CHROME_PATH
if it is not at a standard location). npm consumers do not need these tools.
Publishing
The package verification workflow runs on pushes and pull requests. The separate
publish workflow is manual; it never publishes merely because code was pushed.
For GitHub Actions publishing, configure the npm environment and either an
npm trusted publisher for publish.yml or an NPM_TOKEN secret authorized for
this scope. The workflow requests provenance and publishes a tested tarball.
Local first publication is also supported after authenticating as an npm account
authorized for @winrisef:
npm run build
npm test
npm run test:package
npm pack --dry-run
npm publish --access publicProvenance is requested by the CI workflow, not forced on local publication.
Never publish until the checks above pass. Updating upstream or Emscripten must
also pass npm run test:repro.
Licensing
The wrapper source is MIT licensed. The packaged JPEGli/Highway-derived
WebAssembly remains subject to the upstream notices in
THIRD_PARTY_NOTICES.md and licenses/, including JPEGli's PATENTS grant.
Redistributions must retain those files. See those texts for the controlling
terms.
