PNM to BMP API
POST a .ppm, .pgm, .pbm or .pam file to /v2/convert/pnm/to/bmp and get an uncompressed bitmap. An opaque image is written with 24 bits per pixel: the 230,415-byte sample PPM became a 230,454-byte BMP with the same pixels.
- Endpoint
- POST /v2/
convert/ pnm/ to/ bmp - Accepts
- pnm
- 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/pnm/to/bmp \
-H "Authorization: Bearer $PDFCONVERSIONAPI_KEY" \
-F File=@three-spheres-render.ppm \
-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/pnm/to/bmp",
headers={"Authorization": f"Bearer {key}"},
files=[("File", open("three-spheres-render.ppm", "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 = "three-spheres-render.ppm";
form.append("File", new Blob([await readFile(file)]), file);
const url = "https://api.pdfconversionapi.com/v2/convert/pnm/to/bmp";
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": "three-spheres-render.bmp",
"FileExt": "bmp",
"FileSize": 230454,
"FileData": "Qk02hAMAAAAAADYAAAAoAAAA…"
}
]
}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
- three-spheres-render.ppm (225 KB)
- Output
- three-spheres-render.bmp (225 KB)
- Source
- A 320 x 240 frame from a small ray tracer we wrote for this page (three spheres on a checkered floor), saved as a binary PPM (P6) with 8 bits per channel. (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 | Bitmap width; ImageWidth=128 gives 128 × 96 and 36,918 bytes.Values: 1–20000 |
ImageHeightinteger | Bitmap height, or the box height together with a width.Values: 1–20000 |
ScaleProportionsboolean, default true | false stretches to the exact box; a 200 × 200 box then gives a square bitmap instead of 200 × 150. |
ScaleIfLargerboolean, default false | true avoids enlarging the frame, which in a 24-bit BMP grows the file by 3 bytes for every added pixel. |
ScaleImageboolean, default true | false crops the top-left area of the requested size. |
ColorSpaceenum, default default | Changes pixel values only: gray and monochrome still write a 24-bit bitmap of 230,454 bytes, with red, green and blue equal.Values: default, rgb, gray, monochrome |
ImageResolutioninteger | Stored as pixels per meter in the header (300 writes 11,811). Left out, the fields are 0.Values: 10–800 |
ImageInterpolationboolean, default false | Accepted; resizing is always Lanczos. |
FileNamestring | Base name of the .bmp; defaults to the upload's. |
StoreFileboolean, default false | Return a link kept for 3 hours instead of 300 KB of base64. |
What carries over, and what doesn't
Kept
- Every pixel: the sample BMP holds exactly the PPM's RGB values
- Alpha from PAM files: a P7 RGB_ALPHA test file became a 32-bit bitmap that kept alpha 0, 128 and 255
- The 320 × 240 frame size
Lost or not yet supported
- Compact gray storage. A 76,815-byte PGM became a 230,454-byte 24-bit bitmap, three times larger, since there is no 8-bit gray or palette output.
- 16-bit samples, reduced to 8 bits.
- 1-bit packing: a 9,611-byte PBM also became a 230,454-byte 24-bit bitmap.
Limits
- One file per request.
- Requests up to 10 MB on the Free plan or 100 MB on paid plans, all files and fields together.
- 100,000,000 pixels per image, also after resizing.
- 120 seconds per conversion, then 504.
Where teams use it
Tools that read BMP but not netpbm
If a Windows utility, paint program or embedded toolchain you rely on opens bitmaps but not .ppm or .pgm, converting lets it use frames written by netpbm-based code.
Raw BGR buffers
The BMP's pixels start at byte 54 as uncompressed blue, green and red bytes, bottom row first. Rows are padded to a multiple of 4 bytes; a 320 px row is 960 bytes, so the sample has no padding. They can be copied into a texture or framebuffer without a decoder.
Questions
Is the BMP bigger than my PPM?
Barely, for an opaque image. Both store 3 bytes per pixel, and the BMP's header is 54 bytes against the PPM's 15, so the 320 × 240 sample went from 230,415 to 230,454 bytes. Widths whose rows don't fill a multiple of 4 bytes add a little padding per row. A PAM with transparency becomes a 32-bit bitmap: 307,322 bytes at the same size.
Can I get a 24-bit or 8-bit BMP?
24-bit is what any opaque input gives: PPM, PGM, PBM, or a PAM whose alpha is 255 everywhere. A PAM with transparency gives 32 bits per pixel. There is no 8-bit or palette option; ColorSpace=gray makes the pixels gray without changing the format.
Does the bitmap keep the alpha of a PAM file?
Yes. A PAM with any transparent pixel gives a 32-bit bitmap whose header's alpha mask marks the fourth byte as alpha, and a test PAM with transparent and half-transparent rows kept those values exactly.
Convert your first PNM file in a minute.