New:Introducing Socket Scanning for VS Code Marketplace Extensions.Learn more →
Get Started

jpegtran-js

Package Overview
Dependencies
Maintainers
1
Versions
3
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install
Package was removed
Sorry, it seems this package was removed from the registry

jpegtran-js

Lossless JPEG transformations (rotate, flip, crop) in the DCT domain as a WebAssembly module, built from IJG libjpeg 10.

latest
Source
npmnpm
Version
0.9.0
Version published
Weekly downloads
0
-100%
Maintainers
1
Weekly downloads
 
Created
Source

jpegtran-js

Lossless JPEG rotation as a WebAssembly module, built from the Independent JPEG Group's libjpeg, release 10 (jpegsrc.v10.tar.gz) together with its transupp.c transform engine.

The quantized DCT coefficients are rearranged — never dequantized, never requantized — so a rotation causes no quality loss and no generation loss. It is the WebAssembly equivalent of

I will maintaine and update this package, so don't worry because it's new.

jpegtran -rotate 90 -perfect -copy all -outfile output.jpg input.jpg

except that everything happens in memory (via jpeg_mem_src / jpeg_mem_dest): no filesystem, no subprocess, no native binary.

Layout

pathwhat
src/jpegtran_wrapper.cthe exported C API: transform, perfect query, error reporting
index.mjsJavaScript wrapper (createRotator()) and the transform/copy/status constants
build.shbuilds dist/jpegtran.mjs + dist/jpegtran.wasm from the libjpeg sources
test.mjsdifferential test against the system jpegtran/djpeg
test/simple.mjssmall smoke test on the bundled sample image
test/fixtures/lenna.jpgthe sample image (512×512, baseline 4:2:0)
LICENSEthe IJG licence, plus the notes this project has to add
jpeg/throwaway build copy of the libjpeg sources (regenerated by build.sh)
dist/the build output

Build

Requires Emscripten. On this machine it is a system package that is only on the PATH of login shells, so build.sh sources it itself.

./build.sh

The libjpeg sources are taken from, in this order:

  • a build tree that is already there under jpeg/,
  • the source tree given in JPEG_SRC,
  • the official release archive, which is downloaded from https://www.ijg.org/files/jpegsr10.zip into jpegsr10.zip and unpacked under jpeg/.
JPEG_SRC=/path/to/jpeg-10 ./build.sh        # use a local source tree
JPEG_URL=https://example.org/jpegsr10.zip ./build.sh   # use a different mirror

The archive is kept in the project directory, so a second build does not download again. Two quirks of the IJG archive are handled automatically: it is packed without Unix permissions and with CRLF line endings, which would otherwise break the shebang of configure.

The result is tiny: ~176 KB wasm plus a 13 KB JS loader.

Usage

import { createRotator, TRANSFORM, COPY } from "./index.mjs";
import { readFileSync, writeFileSync } from "node:fs";

const rotator = await createRotator();
console.log(rotator.version()); // "10  25-Jan-2026"

const input = readFileSync("photo.jpg");

// rotate 90 degrees clockwise, keeping Exif and every other APPn marker
const { data, perfect } = rotator.rotate(input, 90, { copy: COPY.all });
writeFileSync("photo-rotated.jpg", data);
console.log(perfect); // false when the image is not MCU aligned

Lossless cropping, on its own or combined with a transform:

// crop to 200x150 at (100, 50)
const cropped = rotator.crop(input, { width: 200, height: 150, x: 100, y: 50 });
writeFileSync("photo-cropped.jpg", cropped.data);
console.log(cropped.width, cropped.height); // the dimensions actually produced

// the full jpegtran -crop syntax is available as a string
rotator.crop(input, "200x150+100+50");
rotator.crop(input, "200x150-10-10"); // measured from the right/bottom edge
rotator.crop(input, "200x150f+100+50"); // "f" forces the exact size

// crop and rotate in one pass: the crop applies to the rotated image
rotator.transformDetailed(input, { transform: TRANSFORM.rotate90, crop: "200x150+0+0", copy: COPY.all });

Lower-level entry points:

rotator.perfectPossible(input, TRANSFORM.rotate90); // is the transform MCU aligned?
rotator.transform(input, { transform: TRANSFORM.rotate270, copy: COPY.all, perfect: false });
rotator.transformDetailed(input, { transform: TRANSFORM.none, crop: "100x100" }); // { data, width, height }

rotate() mirrors the previous jpegtran behaviour of jpegtran exactly: it reports whether the transform was perfect (-perfect would have accepted it) and performs the transform either way, which is what jpegtran does when -perfect is omitted.

Transform numbers: none, rotate90, rotate180, rotate270, flipHorizontal, flipVertical, transpose, transverse.

Errors are thrown as TransformError with a status of STATUS.notJpeg, STATUS.notPerfect, STATUS.badCrop, STATUS.library or STATUS.badArgument, plus the IJG message and its message code.

About cropping

Like jpegtran -crop, cropping is lossless, but it is not exact unless you ask for it: JPEG coefficients are stored in iMCU blocks, so IJG silently moves the upper left corner of the region to an iMCU boundary and grows the region so that it still covers what you asked for. The lower right corner stays where it was. Add the f suffix (or use JCROP_FORCE semantics via the string spec) to get exactly the requested dimensions instead. -perfect looks at the whole image, not at the crop region, exactly like jpegtran.

Test

A quick smoke test on the bundled sample image:

node test/simple.mjs

It rotates test/fixtures/lenna.jpg, writes the results to test/out/ and checks that the output is a valid JPEG, that the JFIF segment survives, that 90 + 270 degrees decodes back to the original pixels, and that the bytes match jpegtran -rotate 90 -copy all exactly. The two comparisons need djpeg/jpegtran and are skipped when those are not installed.

The thorough test compares against the system jpegtran on a whole fixture corpus:

node test.mjs [fixture-dir]      # default: ../lossless-rotate/test/fixtures

The corpus covers 4:2:0 / 4:2:2 / 4:4:4, baseline and progressive, MCU-aligned and not, with and without Exif.

Current result on that corpus:

  • byte-identical output to jpegtran for every fixture and every rotation (201/201),
  • the "perfect" predicate agrees with jpegtran -perfect in every case,
  • Exif and the other APPn markers survive unchanged,
  • non-JPEG input is rejected with STATUS.notJpeg.

Notes

  • -copy all must be requested explicitly. The IJG default is JCOPYOPT_COMMENTS, i.e. only COM markers — Exif would be dropped. COPY.all maps to JCOPYOPT_ALL.
  • The output uses the standard Annex K Huffman tables, like jpegtran without -optimize, so it can be slightly larger than the input. That is expected and still lossless.
  • Exif orientation is a tag inside a copied marker; the transform does not rewrite it (same as jpegtran). Rotating a photo that already carries an orientation tag leaves that tag untouched.
  • 8-bit samples only (BITS_IN_JSAMPLE == 8). 12/16-bit JPEG would need a second build.

Licence and provenance

This software is based in part on the work of the Independent JPEG Group.

The IJG licence is reproduced verbatim in LICENSE, together with the notes the licence requires this project to add (which files are new and which IJG code is used unmodified).

  • libjpeg release 10, 25-Jan-2026 — Independent JPEG Group (Thomas G. Lane, Guido Vollbeding), including its transupp.c transform engine. Used unmodified; the libjpeg sources are not vendored in this repository.
  • Emscripten 6.0.9 (system package), no SIMD/nasm — the IJG code is pure C, which is why it builds for WebAssembly without changes.
  • test/fixtures/lenna.jpg is the classical Lenna test image, included only as a sample photograph for the smoke test. It is not covered by the licence above.

Keywords

jpeg

FAQs

Package last updated on 16 Oct 2026

Related posts