BMP to PNM API
POST a bitmap to /v2/convert/bmp/to/pnm and receive its pixels as uncompressed 8-bit RGB behind a binary PPM (P6) header. For a 24-bit BMP the pixel data stays the same size; the header, row order and byte order change.
- Endpoint
- POST /v2/
convert/ bmp/ to/ pnm - Accepts
- bmp
- Sample timing
- 1 ms median of 5 runsHow we measure
- Request size
- Up to 10 MB on the Free plan or 100 MB on paid plans, all files together
Request and response
Every sample below ran against the API before this page was published. Set PDFCONVERSIONAPI_KEY to your key and run it as is.
curl -sS -X POST \
https://api.pdfconversionapi.com/v2/convert/bmp/to/pnm \
-H "Authorization: Bearer $PDFCONVERSIONAPI_KEY" \
-F File=@shelf-label.bmp \
-o response.json
# FileData is base64. Save the first file in converted/,
# or print the JSON error.
if jq -e '.Files' response.json > /dev/null; then
mkdir -p converted
name=$(jq -r '.Files[0].FileName' response.json)
jq -r '.Files[0].FileData' response.json \
| base64 --decode > "converted/$name"
else
cat response.json
fiimport base64
import os
import requests
key = os.environ["PDFCONVERSIONAPI_KEY"]
response = requests.post(
"https://api.pdfconversionapi.com/v2/convert/bmp/to/pnm",
headers={"Authorization": f"Bearer {key}"},
files=[("File", open("shelf-label.bmp", "rb"))],
timeout=180,
)
result = response.json()
if not response.ok:
raise SystemExit(f"{result['Code']}: {result['Message']}")
os.makedirs("converted", exist_ok=True)
for file in result["Files"]:
path = os.path.join("converted", file["FileName"])
with open(path, "wb") as out:
out.write(base64.b64decode(file["FileData"]))
print(path, file["FileSize"], "bytes")
import { mkdir, readFile, writeFile } from "node:fs/promises";
import { join } from "node:path";
const form = new FormData();
const file = "shelf-label.bmp";
form.append("File", new Blob([await readFile(file)]), file);
const url = "https://api.pdfconversionapi.com/v2/convert/bmp/to/pnm";
const key = process.env.PDFCONVERSIONAPI_KEY;
const response = await fetch(url, {
method: "POST",
headers: { Authorization: `Bearer ${key}` },
body: form,
});
const result = await response.json();
if (!response.ok) {
throw new Error(`${result.Code}: ${result.Message}`);
}
await mkdir("converted", { recursive: true });
for (const file of result.Files) {
const path = join("converted", file.FileName);
await writeFile(path, Buffer.from(file.FileData, "base64"));
console.log(path, file.FileSize, "bytes");
}
{
"ConversionCost": 1,
"ConversionTime": 0.001,
"Files": [
{
"FileName": "shelf-label.pnm",
"FileExt": "pnm",
"FileSize": 300015,
"FileData": "UDYKNDAwIDI1MCAyNTUKH2+L…"
}
]
}Response from our test run on 2026-11-03. ConversionTime shows the median of 5 runs. FileData is shortened here; the real response carries the whole file as base64.
The sample, converted
The same file the code sends. Download the input and the output and compare them yourself.

- Input
- shelf-label.bmp (293 KB)
- Output
- shelf-label.pnm (293 KB)
- Source
- A 24-bit warehouse bin label with a QR code and a colored header, drawn by us for this page. (CC0-1.0). How it was made
- Made by
- 2026-11-04, API build
9eeeb0640aba
Parameters
Send these as form fields next to the file. Names are case-insensitive. If you set one of the documented options below that we don't support yet, the request fails with a 400 that names it instead of returning a file made without it.
| Parameter | What it does |
|---|---|
ImageWidthinteger | Scales before writing; 200 gave 200 × 125, 75,015 bytes.Values: 1–20000 |
ImageHeightinteger | Scales to this height; with ImageWidth, the bitmap fits inside both.Values: 1–20000 |
ScaleProportionsboolean, default true | false stretches into the full width and height. |
ScaleIfLargerboolean, default false | true never upsizes. |
ScaleImageboolean, default true | false crops the top-left area, 45,015 bytes for 150 × 100. |
ColorSpaceenum, default default | gray writes a one-channel PGM (P5) of 100,015 bytes for the label, a third of the color file; monochrome uses the same layout with only the levels 0 and 255.Values: default, rgb, gray, monochrome |
AlphaColorstring, default white | Fill for transparent pixels, which only 32-bit BMPs with an alpha mask have.Values: Color name, #RRGGBB or R,G,B |
FileNamestring | Output file name, .pnm appended. |
StoreFileboolean, default false | Return a URL valid for 3 hours. |
What carries over, and what doesn't
Kept
- Every pixel: the sample's raw RGB matches the BMP exactly
- The byte count of the pixel data: 300,000 bytes in both files
- 400 × 250 size
Lost or not yet supported
- Compactness of low bit depths: a 1-bit BMP of 13,062 bytes became the same 300,015-byte RGB file.
- The BMP's DPI; netpbm has no field for it.
- Transparency in 32-bit BMPs with an alpha mask: P6 has no alpha, so clear pixels take
AlphaColor, white by default.
Limits
- One BMP per request; output is 8-bit RGB, or 8-bit gray with
ColorSpace=grayormonochrome. - Requests up to 10 MB on the Free plan or 100 MB on paid plans, all files and fields together.
- 413 for bitmaps over 100,000,000 pixels.
- 1 to 20,000 px per side for resizing.
- 120-second limit (504 after it).
Where teams use it
Test images for vision and DSP code
Netpbm data is a short text header followed by raw samples, easy to load in code with no image library. A BMP from a camera or a capture card converts into that form directly.
Unix image tools
Classic command-line filters read and write netpbm. Converting a bitmap to PNM lets it enter such a pipeline as a plain binary P6 file.
Questions
Is the file PPM or PAM?
PPM. The file starts with P6, a newline, then 400 250 255 and a newline (width, height and maximum value, 15 bytes in all), and then 300,000 bytes of RGB, top row first. With ColorSpace=gray or monochrome it is a P5 graymap instead.
Why is the PNM almost exactly the size of the BMP?
Both are uncompressed 3-byte RGB. The BMP has a 54-byte header and the PPM a 15-byte one, so the files are 300,054 and 300,015 bytes.
Can I get a one-channel PGM?
Yes. ColorSpace=gray returns a P5 file with one byte per pixel, 100,015 bytes and 237 gray levels for the label; monochrome keeps the same layout with just 0 and 255.
Convert your first BMP file in a minute.