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

@nktkas/bmp

v5.0.0

Published

A fast, lightweight, zero-dependency BMP image encoder/decoder written in pure JavaScript.

Readme

BMP Encoder/Decoder

npm JSR bundlejs

A fast, lightweight, zero-dependency BMP image encoder/decoder written in pure JavaScript.

Works with:

Usage

Decode

Supported BMP formats:

  • Header types: BITMAPCOREHEADER, OS22XBITMAPHEADER, BITMAPINFOHEADER, BITMAPV2–V5
  • Compression: BI_RGB, BI_RLE8, BI_RLE4, BI_BITFIELDS, BI_ALPHABITFIELDS, Modified Huffman, RLE24
  • Bit depths: 1, 2, 4, 8, 16, 24, 32, 64
  • Row order: top-down and bottom-up

Full list of supported BMP formats here

Basic usage

import { decode } from "@nktkas/bmp";

const file = new Uint8Array([/* ... BMP file bytes ... */]);
const raw = decode(file);
// { width: 1, height: 1, channels: 3, data: Uint8Array(3) [0, 0, 0] }
//                                  ^
//                                  may be 1 (grayscale), 3 (RGB), or 4 (RGBA)

BI_JPEG / BI_PNG compressed images

BMP files can embed JPEG or PNG data as pixel payload. Use extractCompressedData to get the embedded data, then decode it with any JPEG/PNG library.

import { extractCompressedData } from "@nktkas/bmp";

const bmp = new Uint8Array([/* ... BMP file bytes with BI_PNG compression ... */]);
const extracted = extractCompressedData(bmp);
// { width: 1, height: 1, compression: 5, data: Uint8Array(69) [...] }
//                                     ^
//                                     4 = BI_JPEG, 5 = BI_PNG

// Then decode with any JPEG/PNG library
import sharp from "sharp";
const raw = await sharp(extracted.data).raw().toBuffer();

Encode

Supported encoding formats:

  • Bit depths: 1, 4, 8, 16, 24, 32
  • Compression: BI_RGB, BI_RLE8, BI_RLE4, BI_BITFIELDS, BI_ALPHABITFIELDS
  • Header types: BITMAPINFOHEADER, BITMAPV4HEADER, BITMAPV5HEADER
  • Row order: top-down and bottom-up

Basic usage

import { encode } from "@nktkas/bmp";

// A minimal raw image
const raw = {
  width: 2,
  height: 2,
  channels: 3, // 1 (grayscale), 3 (RGB), or 4 (RGBA)
  data: new Uint8Array([ // 2x2 black and white pixels
    0, 0, 0,  255, 255, 255,
    0, 0, 0,  255, 255, 255,
  ]),
} as const;

// Encode to 24-bit BMP (automatic detection of best settings based on raw data)
const bmp = encode(raw);
//    ^^^
//    Uint8Array([...]) containing the BMP file bytes

Advanced options

interface EncodeOptions {
  /**
   * Bit depth to encode at.
   *
   * @default Follows `raw.channels`: 8 for grayscale, 24 for RGB, 32 for RGBA.
   */
  bitsPerPixel?: 1 | 4 | 8 | 16 | 24 | 32;

  /**
   * Compression method:
   * - `0`: BI_RGB, uncompressed.
   * - `1`: BI_RLE8, run-length encoded, 8bpp only.
   * - `2`: BI_RLE4, run-length encoded, 4bpp only.
   * - `3`: BI_BITFIELDS, channel masks, 16 or 32bpp.
   * - `6`: BI_ALPHABITFIELDS, channel masks with alpha, 32bpp only.
   *
   * @default 0
   */
  compression?: 0 | 1 | 2 | 3 | 6;

  /**
   * DIB header format to write:
   * - `BITMAPINFOHEADER` — 40 bytes, most compatible.
   * - `BITMAPV4HEADER` — 108 bytes, includes masks and sRGB color space.
   * - `BITMAPV5HEADER` — 124 bytes, adds ICC profile and rendering intent.
   *
   * @default "BITMAPINFOHEADER"
   */
  headerType?: HeaderType;

  /**
   * Store rows top-down instead of bottom-up.
   *
   * @default false
   */
  isTopDown?: boolean;

  /**
   * Palette for the indexed depths (1, 4, 8), holding at least as many colors as the depth addresses;
   * anything past that count is dropped. Omitting it quantizes the image down to a palette of its own.
   */
  palette?: Color[];

  /**
   * Channel masks for the two bitfield compressions.
   *
   * @default RGB565 at 16bpp, BGRA8888 at 32bpp.
   */
  bitfields?: BitfieldMasks;
}
import { encode } from "@nktkas/bmp";

// A minimal raw image
const raw = {
  width: 2,
  height: 2,
  channels: 3, // 1 (grayscale), 3 (RGB), or 4 (RGBA)
  data: new Uint8Array([ // 2x2 black and white pixels
    0, 0, 0,  255, 255, 255,
    0, 0, 0,  255, 255, 255,
  ]),
} as const;

// Encode to 8-bit indexed BMP with auto-generated 256-color palette
const bmp = encode(raw, { bitsPerPixel: 8 });
//    ^^^
//    Uint8Array([...]) containing the BMP file bytes

Errors

Every failure is a BmpError with a code. An error raised outside the package is wrapped in one of these, with the original in cause.

import { BmpError, decode, extractCompressedData } from "@nktkas/bmp";

const file = await Deno.readFile("image.bmp");

try {
  const raw = decode(file);
} catch (error) {
  if (!(error instanceof BmpError)) throw error;
  if (error.code !== "EMBEDDED_IMAGE") throw error;

  const { data, compression } = extractCompressedData(file);
}

| Code | Thrown by | Meaning | | ------------------------- | :---------------: | ------------------------------------------------------------------------------- | | INVALID_SIGNATURE | decode | The bytes do not begin with a BMP file header. | | UNSUPPORTED_HEADER | decode | The DIB header size matches no BMP header version this package reads. | | UNSUPPORTED_DEPTH | decode | The BMP format defines no pixel layout for this depth under this compression. | | UNSUPPORTED_COMPRESSION | decode | The compression method is one this package does not implement. | | EMBEDDED_IMAGE | decode | The pixel data is a complete JPEG or PNG; extractCompressedData returns it. | | MALFORMED_FILE | decode | The file is shorter than its header declares; cause holds the original error. | | INVALID_DATA_SIZE | encode | The pixel buffer length is not width × height × channels. | | INCOMPATIBLE_OPTIONS | encode | The given depth, compression, row order and palette do not fit together. | | INVALID_DIMENSIONS | decode,encode | The dimensions are not positive, or too large to allocate a buffer for. |

Benchmarks

All benchmarks run on procedurally generated 1024×1024 images.

Milliseconds per operation (lower is better). Bold = fastest in row, — = unsupported.

Decode comparison

Run command: deno bench --allow-read bench/decode.bench.ts

| Format | @nktkas/bmp | @cwasm/nsbmp (WASM) | bmpimagejs | bmp-js | fast-bmp | bmp-ts | | ----------------- | :---------: | :---------------------------------------------------------------: | :----------------------------------------------------: | :--------------------------------------------: | :------------------------------------------------: | :--------------------------------------------: | | BI_RGB 1-bit | 1.4 | 2.4 | 2.7 | 2.8 | — | 3.5 | | BI_RGB 1-bit (gs) | 0.74 | 2.5 | 2.7 | 2.8 | — | 3.5 | | BI_RGB 4-bit | 1.3 | 2.2 | 2.7 | 4.1 | — | 4.2 | | BI_RGB 4-bit (gs) | 0.47 | 1.9 | 2.3 | 3.8 | — | 4.2 | | BI_RGB 8-bit | 1.1 | 1.7 | 2.5 | 4.4 | — | 6.2 | | BI_RGB 8-bit (gs) | 0.56 | 1.7 | 2.6 | 4.4 | 2.3 | 6.3 | | BI_RGB 16-bit | 1.6 | 1.5 | — | 2.2 | — | 12.0 | | BI_RGB 24-bit | 1.1 | 1.4 | 2.2 | 2.4 | 4.4 | 6.0 | | BI_RGB 32-bit | 1.0 | 1.6 | 1.4 | 5.8 | — | 12.9 | | BI_RLE4 | 0.81 | 0.85 | 0.99 | — | — | — | | BI_RLE8 | 0.70 | 0.72 | 0.91 | — | — | — | | BI_RLE8 (gs) | 0.22 | 1.2 | 1.2 | — | — | — | | BI_BITFIELDS 16 | 1.5 | 4.6 | — | — | — | 12.1 | | BI_BITFIELDS 32 | 1.9 | 4.3 | 4.0 | — | — | — |

Encode comparison

Run command: deno bench bench/encode.bench.ts

| Format | @nktkas/bmp | fast-bmp | bmp-js | | ----------------- | :---------: | :------------------------------------------------: | :--------------------------------------------: | | BI_RGB 1-bit | 12.2 | — | — | | BI_RGB 1-bit (gs) | 0.74 | — | — | | BI_RGB 4-bit | 9.6 | — | — | | BI_RGB 4-bit (gs) | 0.89 | — | — | | BI_RGB 8-bit | 38.4 | — | — | | BI_RGB 8-bit (gs) | 0.08 | 3.4 | — | | BI_RGB 16-bit | 1.3 | — | — | | BI_RGB 24-bit | 1.3 | 8.2 | 1.4 | | BI_RGB 32-bit | 1.3 | — | — | | BI_RLE4 | 9.6 | — | — | | BI_RLE8 | 11.7 | — | — | | BI_RLE8 (gs) | 2.2 | — | — | | BI_BITFIELDS 16 | 1.8 | — | — | | BI_BITFIELDS 32 | 4.0 | — | — |

License

@nktkas/bmp is licensed under the MIT License.

Copyright © 2025-present nktkas.