jimp
Jimp is an image decoder, pixel buffer, encoder, and manipulation toolkit implemented in JavaScript. The default package reads and writes PNG, JPEG, BMP, GIF, and TIFF, then provides resize, crop, cover, contain, rotate, composite, text, blur, color, quantization, hashing, and per-pixel operations in Node, browsers, and workers. Its main attraction is avoiding native addons and system image libraries. That portability comes at a cost: transformations run on the JavaScript thread, and the project warns that its codecs are not optimized and may allocate substantial memory.
Jimp is the practical portability pick for modest image jobs when native code is off the table. For production thumbnail pipelines or modern codec work, Sharp is usually faster, leaner in memory, and less likely to turn image processing into event-loop latency.
Use it if
- You need basic image manipulation in a Node 18+, browser, worker, or constrained deployment where native addons are unavailable
- Your workload is small or offline, such as generating test fixtures, avatars, social cards, simple thumbnails, or bitmap text
- You want direct RGBA pixel access and a plugin architecture implemented entirely in JavaScript
- You need one API across CommonJS and ESM with bundled TypeScript declarations and browser exports
- You process large images or latency-sensitive request traffic; Jimp's getting-started guide warns that its JavaScript codecs are not performance-optimized and can allocate a lot of memory
- You need WebP in the default install, AVIF, HEIF, SVG rendering, video frames, color-profile management, or a broad native codec suite; the documented default formats stop at PNG, JPEG, BMP, GIF, and TIFF, while WebP needs a custom WASM build
- You are following old tutorials that use a default Jimp import, positional resize arguments, quality(), or writeAsync(); the v1 migration guide replaces all of those patterns
- You need a small browser dependency; the convenience package directly composes 27 @jimp codec, core, utility, and plugin packages before optional WASM formats
- You must isolate untrusted image decoding from the application process; Jimp decodes and transforms in the JavaScript runtime, so malformed or oversized files share CPU and memory with your app
Setup reality
Jimp 1.6.1 requires Node 18 or newer and has no native build, system library, peer dependency, credential, or configuration requirement. npm install jimp gives ESM, CommonJS, browser exports, TypeScript declarations, six default format families, and the full default plugin set, but it also pulls 27 direct @jimp packages. Version 1 is the first surprise: import { Jimp } from 'jimp' is correct because the default export is gone; read and fromBuffer create decoded images; new Jimp receives an object; resize, crop, cover, contain, flip, and print use options objects; and write, getBuffer, and getBase64 are the async export methods without the old Async suffix. Codec options now belong on write or getBuffer, so JPEG quality is not a mutating image method. The API documentation contains some generated examples that still show positional calls even though current declarations validate options such as { w, h }, so follow the v1 migration guide and types when they disagree. Inputs become uncompressed RGBA buffers and codecs may allocate additional working memory. Limit upload bytes and dimensions outside Jimp, cap concurrency, and move expensive work to worker threads or a job process so a resize does not stall request handling. Browser use is supported, but remote reads still face CORS, file uploads must become ArrayBuffer data, and some plugin combinations need a Buffer polyfill. WebP requires @jimp/wasm-webp plus @jimp/core and a custom Jimp assembled with defaultFormats and defaultPlugins; it is not enabled by installing jimp alone. Fonts are bitmap font assets loaded asynchronously, not arbitrary system fonts. Output type comes from the MIME passed to getBuffer or the destination extension passed to write, and metadata preservation should not be assumed without a test for the codec and file in question.
Patterns
Read, resize, and write an imageread-resize-write
import {Jimp} from 'jimp';
const image = await Jimp.read('./input.png');
image.resize({w: 320});
await image.write('./output.png');Jimp v1 uses a named export and an options object with w and h. Supplying only w preserves the aspect ratio.
Decode a buffer and encode JPEGconvert-buffer-to-jpeg
import {Jimp, JimpMime} from 'jimp';
const image = await Jimp.fromBuffer(uploadBuffer);
const jpeg = await image.getBuffer(JimpMime.jpeg, {
quality: 80,
});Version 1 puts JPEG quality on the export call. The old quality() and getBufferAsync() examples are obsolete.
Create a solid RGBA canvascreate-empty-image
import {Jimp} from 'jimp';
const canvas = new Jimp({
width: 1200,
height: 630,
color: 0xffffffff,
});
await canvas.write('./card.png');Packed colors use RGBA byte order, so 0xffffffff is opaque white and 0x00000000 is transparent black.
Crop a rectangular regioncrop-region
const image = await Jimp.read('./photo.jpg');
image.crop({x: 100, y: 60, w: 800, h: 600});
await image.write('./crop.jpg', {quality: 85});crop mutates the image and current v1 expects x, y, w, and h in one object. Clone first when the original is still needed.
Fill a thumbnail without stretchingmake-cover-thumbnail
const image = await Jimp.read('./photo.jpg');
image.cover({w: 400, h: 300});
await image.write('./thumbnail.jpg', {quality: 82});cover preserves aspect ratio and clips overflow. Use contain when letterboxing is preferable to losing image edges.
Fit an image inside fixed dimensionscontain-with-letterbox
const image = await Jimp.read('./logo.png');
image.background = 0xffffffff;
image.contain({w: 512, h: 512});
await image.write('./logo-square.png');contain keeps the full image and fills unused space with the background color. Transparent and JPEG outputs need deliberate background handling.
Place a translucent watermarkcomposite-watermark
const [photo, mark] = await Promise.all([
Jimp.read('./photo.jpg'),
Jimp.read('./watermark.png'),
]);
mark.resize({w: 180}).opacity(0.6);
photo.composite(mark, photo.width - mark.width - 24, photo.height - mark.height - 24);
await photo.write('./watermarked.jpg', {quality: 85});composite mutates the destination. Resize and fade the watermark before calculating its final placement.
Draw text with a bundled bitmap fontprint-bitmap-text
import {Jimp, loadFont} from 'jimp';
import {SANS_32_WHITE} from 'jimp/fonts';
const image = await Jimp.read('./banner.png');
const font = await loadFont(SANS_32_WHITE);
image.print({font, x: 24, y: 24, text: 'Release 1.6'});
await image.write('./labeled.png');Jimp uses BMFont assets, not CSS or arbitrary installed fonts. Font loading is async, while print mutates synchronously.
Modify RGBA bytes during a scanedit-pixels
const image = await Jimp.read('./input.png');
image.scan((x, y, idx) => {
image.bitmap.data[idx] = 255 - image.bitmap.data[idx]; // red
image.bitmap.data[idx + 1] = 255 - image.bitmap.data[idx + 1]; // green
image.bitmap.data[idx + 2] = 255 - image.bitmap.data[idx + 2]; // blue
});
await image.write('./inverted.png');Each pixel occupies four bytes in RGBA order and idx points to red. Scanning large images is synchronous CPU work.
Apply synchronous filters before one exportchain-color-filters
const image = await Jimp.read('./input.jpg');
image
.greyscale()
.contrast(0.2)
.brightness(0.1)
.rotate(90)
.flip({horizontal: true});
await image.write('./processed.png');Most transformations mutate and return the same image for chaining. Only reading and exporting need await in this sequence.
Clone before producing multiple variantspreserve-original-branch
const original = await Jimp.read('./source.png');
const small = original.clone().resize({w: 320});
const large = original.clone().resize({w: 1280});
await Promise.all([
small.write('./small.png'),
large.write('./large.png'),
]);Transforms mutate their receiver. clone prevents the first resize from changing the pixels used for later variants.
Build a Jimp class with WebP supportadd-webp-format
import {createJimp} from '@jimp/core';
import {defaultFormats, defaultPlugins} from 'jimp';
import webp from '@jimp/wasm-webp';
const WebpJimp = createJimp({
formats: [...defaultFormats, webp],
plugins: defaultPlugins,
});
const image = await WebpJimp.read('./input.webp');
await image.write('./output.webp');Install @jimp/core and @jimp/wasm-webp explicitly. WebP is not part of the default Jimp format list and WASM bundling may need platform-specific configuration.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| sharp | npm | Choose it for high-throughput server resizing, streaming, modern codecs, metadata controls, and lower CPU use through libvips |
| pureimage | npm | Choose it for a smaller pure-JavaScript Canvas-like drawing API when Jimp's full plugin collection is unnecessary |
| canvas | npm | Choose it when server code needs the HTML Canvas 2D API, font rendering, paths, and drawing semantics despite native installation concerns |