PDN to BMP API
POST a .pdn to /v2/convert/pdn/to/bmp and get the merged layers as an uncompressed Windows bitmap. A canvas with clear parts, like the sample sign, gets 32 bits per pixel and a version 4 header whose alpha mask keeps them clear; a fully opaque canvas gets a 24-bit file.
- Endpoint
- POST /v2/
convert/ pdn/ to/ bmp - Accepts
- pdn
- Sample timing
- 23 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/pdn/to/bmp \
-H "Authorization: Bearer $PDFCONVERSIONAPI_KEY" \
-F File=@lemonade-stand-sign.pdn \
-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/pdn/to/bmp",
headers={"Authorization": f"Bearer {key}"},
files=[("File", open("lemonade-stand-sign.pdn", "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 = "lemonade-stand-sign.pdn";
form.append("File", new Blob([await readFile(file)]), file);
const url = "https://api.pdfconversionapi.com/v2/convert/pdn/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.023,
"Files": [
{
"FileName": "lemonade-stand-sign.bmp",
"FileExt": "bmp",
"FileSize": 345722,
"FileData": "Qk16RgUAAAAAAHoAAABsAAAA…"
}
]
}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
- lemonade-stand-sign.pdn (87 KB)
- Output
- lemonade-stand-sign.bmp (338 KB)
- Source
- A 360 x 240 price sign with rounded transparent corners that we drew and wrote as a layered PDN file ourselves: seven layers, including Overlay and Multiply blend modes, a semi-transparent shine and a hidden draft layer. (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; the file size follows exactly. 180 px gave 180 × 120 and 86,522 bytes, a quarter of the full-size file.Values: 1–20000 |
ImageHeightinteger | Bitmap height, or the second side of a box when sent with ImageWidth.Values: 1–20000 |
ScaleProportionsboolean, default true | false forces both sizes: 240 × 240 came to 230,522 bytes. |
ScaleIfLargerboolean, default false | true never enlarges; ImageWidth=720 then kept 345,722 bytes instead of 1,382,522. |
ScaleImageboolean, default true | false cuts out the top-left region: 180 × 90 is 64,922 bytes. |
ColorSpaceenum, default default | gray and monochrome change the pixel values, not the layout, so the file stays 345,722 bytes.Values: default, rgb, gray, monochrome (also srgb, truecolors, grayscale); cmyk returns 400 |
ImageResolutioninteger | Fills the header's pixels-per-meter fields: 300 became 11,811 in both. Left out, both are 0.Values: 10–800 |
FileNamestring | Base name of the result; without it the file comes back as lemonade-stand-sign.bmp. |
StoreFileboolean, default false | true returns a download link that lasts 3 hours. |
What carries over, and what doesn't
Kept
- Every merged pixel, alpha included: decoded, the BMP equals the PNG route's output for the same file
- Transparency, through a BITMAPV4HEADER with an alpha mask of 0xFF000000: the sign's corners have alpha 0
- The canvas size, 360 × 240, with rows stored bottom-up, the default BMP order
Lost or not yet supported
- Compression. The 89,156-byte PDN became 345,722 bytes.
TransparentColor, which is not applied to BMP:#FFDC5Creturned the same file as no parameters.- Layers, and hidden layers, which are left out.
- Clear corners in readers that skip the alpha mask: their color bytes are 0, 0, 0, so such a reader shows them black.
Limits
- One PDN per request; up to 100,000,000 pixels after merging.
- Requests up to 10 MB on the Free plan or 100 MB on paid plans, all files and fields together.
- 120 seconds per conversion, then 504.
- Bit depth follows the canvas: any clear or partly clear pixel gives 32 bits with the version 4 header, and a fully opaque PDN gives 24 bits with the 40-byte header (a 40 × 30 test canvas came to 3,654 bytes). There is no 8-bit or palette option.
Where teams use it
Windows programs that read BMP
Some older Windows programs, installers and game mods load only BMP for logos and backgrounds. The .pdn master can be sent as it is, without opening Paint.NET to save a copy.
Pixels at a fixed offset
Firmware tools for small displays read BMP because the pixel array starts at a known offset, 122 bytes here, and needs no decompression.
Questions
Why is the BMP so much larger than the PDN?
It has no compression: 360 × 240 pixels × 4 bytes plus a 122-byte header is exactly 345,722 bytes. The PDN stores its layers gzip-compressed, and a PNG of the same sign is 72,204 bytes.
Does the BMP keep transparency?
Yes, as a fourth byte per pixel, labeled by the alpha mask in the version 4 header. Programs that read BMP alpha show the rounded corners clear; one that ignores it shows them black.
Does the BMP record a DPI?
Only when you send ImageResolution: 300 is stored as 11,811 pixels per meter in both directions. Without it both fields are 0.
Convert your first PDN file in a minute.