bmp-encode
v1.0.0
Published
Encode RGBA pixels to a BMP file, with alpha that survives. Zero dependencies, browser and Node.
Maintainers
Readme
bmp-encode
Encode RGBA pixels to a BMP file — with an alpha channel that actually survives. No dependencies. Works in the browser and in Node.
npm install bmp-encodeWhy this exists
Browsers can read BMP but they cannot write it: canvas.toBlob() has no "image/bmp".
So you write the bytes yourself, which looks like forty lines of DataView calls.
The catch is the header. The obvious choice, a BITMAPINFOHEADER at 32 bits per pixel,
does store your alpha byte — and decoders throw it away. The BMP specification calls
that byte reserved, so Chrome, Firefox and most image libraries ignore it. Your
transparent pixels come back opaque and nothing tells you why.
What makes alpha survive is a BITMAPV4HEADER with BI_BITFIELDS compression and an
explicit alpha mask. That is the whole difference between this library and the snippet
you were about to write.
There is a reproducible check in test/browser.html: it encodes four
pixels with alpha 255, 128, 0 and 200, hands the bytes to the browser as an image, and
reads the decoded pixels back. All four alpha values come back unchanged.
Usage
From a canvas
import { encodeBmpBlob } from "bmp-encode";
const blob = encodeBmpBlob(canvas);
const a = document.createElement("a");
a.href = URL.createObjectURL(blob);
a.download = "image.bmp";
a.click();From ImageData
import { encodeBmp } from "bmp-encode";
const bytes = encodeBmp(ctx.getImageData(0, 0, canvas.width, canvas.height));
// -> Uint8Array containing a complete .bmp fileIn Node
The core takes anything shaped like ImageData, so it does not need a DOM:
import { writeFileSync } from "node:fs";
import { encodeBmp } from "bmp-encode";
writeFileSync(
"out.bmp",
encodeBmp({ width, height, data /* Uint8ClampedArray | Uint8Array, RGBA */ })
);API
encodeBmp(source, options?) => Uint8Array
source is an ImageData, any object with { width, height, data }, or a canvas
(HTMLCanvasElement or OffscreenCanvas) to read pixels from.
Returns the bytes of a complete BMP file.
encodeBmpBlob(source, options?) => Blob
The same bytes wrapped in a Blob with type image/bmp. Browser only.
Options
| Option | Default | Meaning |
| --- | --- | --- |
| pixelsPerMetre | 2835 (~72 dpi) | Resolution written into the header. Affects printing software only; the pixels are unchanged. Use 11811 for 300 dpi. |
Invalid input throws rather than producing a corrupt file: non-positive dimensions, or a
data length that does not match width * height * 4.
What it writes
BITMAPV4HEADER(108 bytes), 32 bits per pixelBI_BITFIELDSwith masks0x00FF0000/0x0000FF00/0x000000FF/0xFF000000- Top-down rows, via a negative height — no row padding is needed at 32bpp
- Pixels as B, G, R, A
Output is roughly width * height * 4 + 122 bytes. BMP does not compress, so a
1920×1080 image is about 8 MB whatever it contains. If the file only needs to be
lossless, PNG is smaller by a wide margin; reach for BMP when something on the other
end insists on it — label printers, till systems, embroidery machines, industrial
controllers and older Windows software all tend to.
Prior art
canvas-to-bmp covers the same ground.
It is an untyped UMD bundle, has not been updated in years, and writes a plain
BITMAPINFOHEADER, so alpha does not survive. This package exists because that was not
good enough for a tool that had to keep transparency.
Tests
npm test # unit tests, node:test
npm run build
open test/browser.html # the alpha round-trip, in a real decoderLicence
MIT. Extracted from the BMP encoder behind edit-tool.com, a set of free image tools that run entirely in your browser.
