EXR to BMP API
POST an .exr to /v2/convert/exr/to/bmp and get an uncompressed BMP. A render with transparency, like the 240 × 300 sample, is written at 32 bits with a 108-byte BITMAPV4 header whose bit masks include alpha (288,122 bytes); a fully opaque render comes back as 24-bit.
- Endpoint
- POST /v2/
convert/ exr/ to/ bmp - Accepts
- exr
- Sample timing
- 24 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/exr/to/bmp \
-H "Authorization: Bearer $PDFCONVERSIONAPI_KEY" \
-F File=@glowing-bulb-render.exr \
-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/exr/to/bmp",
headers={"Authorization": f"Bearer {key}"},
files=[("File", open("glowing-bulb-render.exr", "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 = "glowing-bulb-render.exr";
form.append("File", new Blob([await readFile(file)]), file);
const url = "https://api.pdfconversionapi.com/v2/convert/exr/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.024,
"Files": [
{
"FileName": "glowing-bulb-render.bmp",
"FileExt": "bmp",
"FileSize": 288122,
"FileData": "Qk16ZQQAAAAAAHoAAABsAAAA…"
}
]
}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
- glowing-bulb-render.exr (187 KB)
- Output
- glowing-bulb-render.bmp (281 KB)
- Source
- A glowing light bulb we drew in code and saved as a 240 × 300 OpenEXR render: half-float RGBA, premultiplied alpha, ZIP compression, values up to 12.0. (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 | Pixel width of the bitmap. ImageWidth=120 gave 120 × 150 and 72,122 bytes, which is 4 bytes a pixel plus the header.Values: 1–20000 |
ImageHeightinteger | Pixel height; ImageHeight=100 produced 80 × 100. With ImageWidth it becomes a bounding box (200 × 200 gave 160 × 200).Values: 1–20000 |
ScaleProportionsboolean, default true | false ignores the 4:5 shape and fills the box, so 200 × 200 is exactly square (160,122 bytes). |
ScaleIfLargerboolean, default false | true keeps the bitmap at 240 × 300 when a larger size is asked for; plain ImageWidth=800 wrote 800 × 1000 and 3,200,122 bytes. |
ScaleImageboolean, default true | false crops the top-left corner to the requested size, so 200 × 100 holds the top of the glass. |
ColorSpaceenum, default default | gray and monochrome change the pixel values but the file stays 32 bits per pixel with alpha, the same 288,122 bytes. rgb returns the default file, since this render uses its alpha.Values: default, rgb, gray, monochrome; cmyk returns 400 |
ImageResolutioninteger | Written into the header as pixels per metre: 300 stored 11,811 in both directions. Without it both fields are 0.Values: 10–800 |
FileNamestring | Output name; FileName=studio-bulb returned studio-bulb.bmp. |
StoreFileboolean, default false | true stores the bitmap for 3 hours and answers with a download URL. |
What carries over, and what doesn't
Kept
- Alpha in the fourth byte of every pixel: 26,592 clear and 20,590 partly clear pixels in the sample, behind a 0xFF000000 alpha mask
- Each 16-bit level rounded to the nearest 8-bit one: the BMP equals the PNG route's values divided by 257, on every pixel
- The frame size and orientation; rows are stored bottom-up, as the positive height of 300 says
Lost or not yet supported
- Precision. 16-bit samples become 8-bit, leaving 1,352 distinct RGBA values (1,164 distinct colors) in the sample.
- Highlights over 1.0: the 12.0 filament and the brightest glass are 255 in every channel.
- Linear-to-display conversion. A stored 0.5 is written as 128 and 0.18 as 46, with nothing in the file that tells a viewer the values are linear.
- Correct soft edges. Colors are not divided by alpha, so halo pixels are darker than the render intends.
- Compactness: no compression, so the file is 1.5 times the size of the EXR.
Limits
- A single EXR per request.
- 10 MB on the Free plan or 100 MB on paid plans per request in total.
- A 413 above 100,000,000 pixels.
- Resize targets between 1 and 20,000 px.
- Requests time out after 120 seconds.
Where teams use it
Windows tools that read only BMP
Older Windows utilities, embedded displays and some print or signage software accept BMP but not EXR. Converting the render on the server avoids installing an EXR reader on those machines.
Raw pixels for simple code
A BMP is a header followed by uncompressed BGRA rows, which a few lines of C or Python can read without a decoder library. That suits test harnesses comparing render output.
Questions
Does the BMP keep the render's transparency?
Yes. The file is 32 bits per pixel with BI_BITFIELDS masks, and the top byte is alpha. Whether a program shows it depends on that program reading the alpha mask; the clear pixels' color bytes are black or within 1 level of it.
Why is the BMP bigger than the EXR?
BMP stores every pixel raw at 4 bytes, while the EXR used ZIP compression. For 240 × 300 that is 288,000 bytes of pixels plus a 122-byte header, against 191,302 bytes of EXR.
Can I get a 24-bit BMP without alpha?
Yes, when the render is opaque. A render with no A channel, or with alpha 1.0 on every pixel, comes back as a 24-bit BMP with the 40-byte header: a 64 × 48 test render was 9,270 bytes. A render with any transparency stays 32-bit whatever ColorSpace you send. Use exr/to/jpg or exr/to/pnm with AlphaColor when you need the transparency flattened.
Are the colors the same as in my render viewer?
Only for values that are already display-ready. The API writes the stored linear numbers clipped to 0 to 1, with no view transform, so a linear render looks darker and more contrasty than in a viewer that applies sRGB or a film look.
Convert your first EXR file in a minute.