mrkeyoor.com_
Wed 23 Sept 00:35 UTC
npmUtilsupdated 22 Sept 2026

bmp-js review

bmp-js 0.1.0 is a small Node codec for one old image format. It reads 1, 4, 8, 16, 24, and 32-bit BMP files, including RLE4, RLE8, and bitfield branches in the decoder, then returns header fields plus four bytes per pixel in ABGR order. Its writer accepts that same byte order but always emits an uncompressed 24-bit BMP, so alpha, palettes, compression, and the source bit depth do not survive a round trip. The current version is still the 2018 release. It has no runtime dependencies or TypeScript declarations, and our browser bundle test produced 9.2 KB minified and 2.3 KB gzipped even though the package was written for Node Buffers.

Verdict

bmp-js 0.1.0 installed in 0.5 seconds with 1 package, 1 MB on disk, and 0 audit findings in our sandbox, making it cheap for a Node job that only has to decode BMP or emit 24-bit BMP. Skip it for untrusted uploads without your own limits, alpha-preserving output, or a general image pipeline.

We installed it

Lab card: what happened when we installed bmp-jsScreenshot of bmp-js documentation
Install✓ · 0.5s1 package on disk · 1 MB
ImportESM import works · require() works · CommonJS package
Browser2.3 KBgzipped (9.2 KB minified), bundled with esbuild
Typesno TypeScript types found
Known vulns00 critical · 0 high · 0 moderate · 0 low (npm audit)

Answers from our run

Does bmp-js install cleanly?

Yes. In a fresh container with an empty cache, npm install bmp-js finished in 0.5s, leaving 1 package and 1 MB on disk. npm audit reported no known vulnerabilities.

How much does bmp-js add to a browser bundle?

2.3 KB gzipped (9.2 KB minified) when the whole package is bundled for the browser with esbuild. Importing only part of it is usually smaller.

Does bmp-js work with both ESM and CommonJS?

Yes. Both import 'bmp-js' and require('bmp-js') worked in Node 22 in our run. The package is published as CommonJS.

Does bmp-js include TypeScript types?

No type declarations were found in our install, so TypeScript users need their own declarations.

bmp-js or sharp: which should you use?

sharp: Use it for production resizing, conversion, metadata work, and a wider set of image formats when native binaries are acceptable. bmp-js 0.1.0 installed in 0.5 seconds with 1 package, 1 MB on disk, and 0 audit findings in our sandbox, making it cheap for a Node job that only has to decode BMP or emit 24-bit BMP.

When should you not use bmp-js?

Choose sharp or Jimp when the job includes resizing, compositing, metadata edits, or formats such as PNG, JPEG, WebP, GIF, and TIFF; bmp-js only handles BMP coding

API stability4/5bmp-js 0.1.0 has kept a two-function CommonJS interface since its 2018 npm release: `decode(buffer)` and `encode(image, quality)`. The README and current source agree on the ABGR data layout and 24-bit writer. That small surface is easy to pin with fixtures, but the package remains below 1.0 and quirks such as the unused quality argument have never been replaced by a documented compatibility policy.
Docs3/5The README names all 6 supported decode bit depths, lists returned header fields, states the ABGR byte order, and says encoding is limited to 24-bit BMP. It leaves out synchronous execution, allocation from untrusted header dimensions, the zero alpha byte used for 24-bit input, and the lossy nature of re-encoding. Its write example also uses `fs.WriteFileSync`, whose capital W fails in Node.
Maintenance2/5The npm package has remained at v0.1.0 since April 2018, while the repository's latest push was 2023-11-04. GitHub shows 91 stars and 20 open issues and pull requests, and the repository is neither archived nor disabled. Users still receive old Buffer constructor calls, no declarations, and no new package release containing later maintenance work, so production adopters should treat the codec as frozen code.
Ecosystem3/5bmp-js recorded 5,141,368 downloads for 2026-08-18 through 2026-08-24, and 0 direct dependencies make it easy for Node tools to carry. Its ecosystem boundary is sharp: there are no plugins, transforms, streams, browser entry, typed adapters, or helpers for the RGBA order used by Canvas and most JavaScript imaging code. It fits as a leaf codec rather than the center of an image stack.

Use it if

  • A Node job must read legacy BMP files without installing native image binaries
  • Your inputs include paletted, RLE4, RLE8, 16-bit, 24-bit, or 32-bit BMP variants
  • Your output requirement is limited to an uncompressed 24-bit BMP
  • You can convert the package's ABGR bytes at the boundary of an RGBA-based pipeline
Skip it if

Setup reality

Our install of bmp-js 0.1.0 finished in 0.5 seconds and left 1 package using 1 MB on disk. npm audit reported 0 known vulnerabilities. The package has 0 direct dependencies and 0 peer dependencies, occupies 720 KB unpacked, and uses the MIT license. require() and ESM import both worked in Node 22, although the published package is CommonJS with no exports map or TypeScript declarations.

There are no credentials, environment variables, native builds, or config files. The setup trap is the pixel contract: decode(buffer) returns ABGR, and encode({ data, width, height }) expects ABGR. A 24-bit decode places 0 in the alpha byte. Convert channel order explicitly before passing pixels to an RGBA consumer, and choose whether that zero should become 255.

Both operations are synchronous and take a complete Node Buffer. The API has no stream, abort signal, worker pool, or cache. A large conversion therefore holds the source and decoded pixel buffer in memory while blocking its event-loop thread. Move unpredictable workloads to a worker or offline queue. The source derives its allocation from BMP header dimensions, so enforce file-byte and pixel-count limits before decode.

Encoding has a smaller feature set than decoding. It writes a top-down, uncompressed 24-bit BMP and drops alpha, palette data, original compression, and original bit depth. The optional quality argument defaults to 100 in source but never changes the bytes. The README example also calls fs.WriteFileSync; Node's actual method is fs.writeFileSync. Our esbuild browser test reached 9.2 KB minified and 2.3 KB gzipped, but Buffer-based file handling still needs a browser adapter.

Patterns

Decode a BMP buffer decode-buffer

const fs = require('node:fs')
const bmp = require('bmp-js')

const source = fs.readFileSync('./scan.bmp')
const image = bmp.decode(source)
console.log({ width: image.width, height: image.height, bitPP: image.bitPP })

`decode()` runs synchronously and returns 4 bytes per pixel in `image.data`; the byte order is ABGR.

Load the CommonJS package from ESM import-from-esm

import bmp from 'bmp-js'
import { readFile } from 'node:fs/promises'

const image = bmp.decode(await readFile('./scan.bmp'))

Node 22 can import this CommonJS default export, but bmp-js 0.1.0 has no native ESM entry or exports map.

Read one ABGR pixel read-pixel

function readPixel(image, x, y) {
  if (x < 0 || y < 0 || x >= image.width || y >= image.height) {
    throw new RangeError('pixel outside image')
  }
  const i = (y * image.width + x) * 4
  return {
    a: image.data[i],
    b: image.data[i + 1],
    g: image.data[i + 2],
    r: image.data[i + 3],
  }
}

The first byte is alpha and the fourth is red; 24-bit source files decode with alpha set to 0.

Convert decoded pixels to RGBA convert-abgr-to-rgba

function abgrToRgba(image) {
  const rgba = Buffer.alloc(image.data.length)
  for (let i = 0; i < image.data.length; i += 4) {
    rgba[i] = image.data[i + 3]
    rgba[i + 1] = image.data[i + 2]
    rgba[i + 2] = image.data[i + 1]
    rgba[i + 3] = image.bitPP === 24 ? 255 : image.data[i]
  }
  return rgba
}

This conversion changes 24-bit alpha from the decoder's 0 to opaque 255, which is an application choice rather than package behavior.

Prepare RGBA pixels for encoding convert-rgba-to-abgr

function rgbaToAbgr(rgba) {
  const abgr = Buffer.alloc(rgba.length)
  for (let i = 0; i < rgba.length; i += 4) {
    abgr[i] = rgba[i + 3]
    abgr[i + 1] = rgba[i + 2]
    abgr[i + 2] = rgba[i + 1]
    abgr[i + 3] = rgba[i]
  }
  return abgr
}

The encoder expects 4 input bytes per pixel in ABGR order even though its BMP output contains only 3 color bytes per pixel.

Write an uncompressed BMP encode-file

const fs = require('node:fs')
const bmp = require('bmp-js')

const result = bmp.encode({ data: abgr, width, height })
fs.writeFileSync('./result.bmp', result.data)

bmp-js 0.1.0 always writes top-down, uncompressed 24-bit BMP data and discards the input alpha bytes.

Create a solid-color BMP create-solid-image

const width = 80
const height = 50
const abgr = Buffer.alloc(width * height * 4)
for (let i = 0; i < abgr.length; i += 4) {
  abgr.set([255, 30, 120, 220], i)
}
const file = require('bmp-js').encode({ data: abgr, width, height }).data

Each pixel uses 4 ABGR bytes in memory; the leading 255 is ignored when the 24-bit file is encoded.

Change a decoded pixel in place edit-pixel

function paintRed(image, x, y) {
  const i = (y * image.width + x) * 4
  image.data[i] = 255
  image.data[i + 1] = 0
  image.data[i + 2] = 0
  image.data[i + 3] = 255
}

paintRed(image, 0, 0)

bmp-js performs no coordinate or channel validation, and a later encode drops the alpha byte at offset 0.

Inspect the decoded BMP format inspect-header

const image = bmp.decode(buffer)
console.log({
  fileSize: image.fileSize,
  width: image.width,
  height: image.height,
  bitsPerPixel: image.bitPP,
  compression: image.compress,
  paletteSize: image.palette?.length ?? 0,
})

`decode()` exposes input header values, but `encode()` does not preserve the input palette, compression code, or bit depth.

Reject oversized dimensions before decode limit-pixel-count

function decodeBmpWithLimit(buffer, maxPixels = 16_000_000) {
  if (buffer.length < 26 || buffer.toString('ascii', 0, 2) !== 'BM') {
    throw new Error('invalid BMP header')
  }
  const width = buffer.readUInt32LE(18)
  const height = Math.abs(buffer.readInt32LE(22))
  if (!width || !height || width > Math.floor(maxPixels / height)) {
    throw new Error('BMP exceeds pixel limit')
  }
  return require('bmp-js').decode(buffer)
}

The decoder allocates `width * height * 4` bytes from header values, so callers must set their own pixel and upload-byte limits.

Return encoded BMP from an HTTP handler serve-http-response

const output = bmp.encode({ data: abgr, width, height }).data
res.statusCode = 200
res.setHeader('Content-Type', 'image/bmp')
res.setHeader('Content-Length', output.length)
res.end(output)

`encode()` returns the complete file as a Buffer, so this response path holds the whole BMP in memory instead of streaming it.

Decode outside the server event loop move-to-worker

// bmp-worker.cjs
const { parentPort } = require('node:worker_threads')
const bmp = require('bmp-js')

parentPort.on('message', (buffer) => {
  const image = bmp.decode(Buffer.from(buffer))
  parentPort.postMessage({ width: image.width, height: image.height, data: image.data })
})

Both codec functions are synchronous; a worker thread keeps a large 4-byte-per-pixel decode off the request-handling event loop.

Alternatives

PackageRegistryPick it when
sharpnpmUse it for production resizing, conversion, metadata work, and a wider set of image formats when native binaries are acceptable.
jimpnpmUse it when a pure JavaScript image API needs transforms as well as decoding and encoding.
pngjsnpmUse it for focused PNG parsing and writing with synchronous and asynchronous APIs.
bmp-tsnpmUse it when a BMP-only workflow needs a package written in TypeScript with published declarations.

More utils guides

lru-cache · ajv · type-fest · p-limit · find-up · js-yaml · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.