Skip to content

nktkas/bmp

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

51 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

BMP Encoder/Decoder

npm JSR bundlejs

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

Works with: browsers Bun Deno Node.js Cloudflare Workers

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 {
  /**
   * Bits per pixel (1, 4, 8, 16, 24, or 32).
   *
   * Default: Auto-detected from input channels
   * - channels=1 (grayscale) → 8-bit
   * - channels=3 (RGB) → 24-bit
   * - channels=4 (RGBA) → 32-bit
   */
  bitsPerPixel?: 1 | 4 | 8 | 16 | 24 | 32;

  /**
   * BMP compression method.
   * - 0 (BI_RGB) - No compression. Raw pixel data.
   * - 1 (BI_RLE8) - 8-bit run-length encoding. 256-color indexed only.
   * - 2 (BI_RLE4) - 4-bit run-length encoding. 16-color indexed only.
   * - 3 (BI_BITFIELDS) - Uncompressed with custom RGB bit masks.
   * - 6 (BI_ALPHABITFIELDS) - Uncompressed with custom RGBA bit masks.
   *
   * Default: 0 (BI_RGB)
   */
  compression?: 0 | 1 | 2 | 3 | 6;

  /**
   * BMP header format.
   * - "BITMAPINFOHEADER": 40 bytes. Most compatible.
   * - "BITMAPV4HEADER": 108 bytes. Includes masks and sRGB color space.
   * - "BITMAPV5HEADER": 124 bytes. Adds ICC profiles and rendering intent.
   *
   * Default: "BITMAPINFOHEADER"
   */
  headerType?: "BITMAPINFOHEADER" | "BITMAPV4HEADER" | "BITMAPV5HEADER";

  /**
   * Row order.
   * - false - bottom-up (standard BMP)
   * - true - top-down
   *
   * Default: false
   */
  isTopDown?: boolean;

  /**
   * Color palette for indexed formats (1, 4, 8-bit).
   * If not provided, palette will be generated automatically.
   */
  palette?: Color[];

  /**
   * Bit masks for BI_BITFIELDS/BI_ALPHABITFIELDS compression.
   *
   * Default: RGB565 for 16-bit, BGRA8888 for 32-bit.
   */
  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

Benchmarks

All benchmarks use the BMP Suite test images (127x64 pixels).

Microseconds per operation (µs; lower is better). Bold = fastest in row, = not supported.

Decode comparison

Run command: deno run -A tests/decode/bench.ts

Format @nktkas/bmp @cwasm/nsbmp bmpimagejs bmp-js fast-bmp bmp-ts
BI_RGB 1-bit 9.3 18.7 18.9 24.9 27.8
BI_RGB 1-bit (gs) 4.7 18.6 18.6 23.5 26.0
BI_RGB 4-bit 10.6 18.8 19.8 34.4
BI_RGB 4-bit (gs) 5.2 19.1 20.3 34.8
BI_RGB 8-bit 11.3 18.5 21.7 46.5 61.8
BI_RGB 8-bit (gs) 6.3 18.7 21.5 46.0 19.5 43.8
BI_RGB 16-bit 12.9 14.5 15.2 19.8 109.3
BI_RGB 24-bit 6.9 12.6 13.3 21.1 32.5 52.4
BI_RGB 32-bit 12.0 14.7 17.9 51.9 116.6
BI_RLE4 12.9 13.8 17.7
BI_RLE8 12.9 14.6 18.1
BI_BITFIELDS 16 14.0 39.2 37.9 106.5
BI_BITFIELDS 32 15.4 38.0 36.2 47.3 118.4

@cwasm/nsbmp uses WebAssembly (compiled C). The other libraries, including @nktkas/bmp, are pure JavaScript.

Encode comparison

Run command: deno run -A tests/encode/bench.ts

Format @nktkas/bmp fast-bmp bmp-js
BI_RGB 1-bit 82.9
BI_RGB 1-bit (gs) 17.1
BI_RGB 4-bit 133.5
BI_RGB 4-bit (gs) 17.3
BI_RGB 8-bit 163.1
BI_RGB 8-bit (gs) 14.0 31.6
BI_RGB 16-bit 13.2
BI_RGB 24-bit 18.4 74.0 30.5
BI_RGB 32-bit 17.4
BI_RLE4 133.9
BI_RLE8 160.7
BI_BITFIELDS 16 13.7
BI_BITFIELDS 32 17.4

About

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

Topics

Resources

License

Stars

Watchers

Forks

Contributors