GIF to PNM API
POST a GIF to /v2/convert/gif/to/pnm and get frame 1 as raw 8-bit RGB in a binary PPM (P6) file. Transparent pixels are filled with white, or with your AlphaColor, because the file has no alpha channel.
- Endpoint
- POST /v2/
convert/ gif/ to/ pnm - Accepts
- gif
- 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/gif/to/pnm \
-H "Authorization: Bearer $PDFCONVERSIONAPI_KEY" \
-F File=@parcel-tracker.gif \
--form-string 'AlphaColor=#EEF1F4' \
-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/gif/to/pnm",
headers={"Authorization": f"Bearer {key}"},
files=[("File", open("parcel-tracker.gif", "rb"))],
data={
"AlphaColor": "#EEF1F4",
},
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 = "parcel-tracker.gif";
form.append("File", new Blob([await readFile(file)]), file);
form.append("AlphaColor", "#EEF1F4");
const url = "https://api.pdfconversionapi.com/v2/convert/gif/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": "parcel-tracker.pnm",
"FileExt": "pnm",
"FileSize": 192015,
"FileData": "UDYKNDAwIDE2MCAyNTUK7vH0…"
}
]
}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
- parcel-tracker.gif (8.7 KB)
- Output
- parcel-tracker.pnm (188 KB)
- Source
- A four-frame animated parcel-tracking card with a transparent outside and a QR code, 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 |
|---|---|
AlphaColorstring, default white | Fill for the GIF's clear pixels. The sample uses a pale gray, #EEF1F4, and its corners read exactly (238, 241, 244) in the output.Values: white, black, red, green, blue, gray, #RRGGBB or R,G,B |
BackgroundColorstring, default white | Second name for the fill, read when AlphaColor is not sent. black gave black corners.Values: Same as AlphaColor |
ImageWidthinteger | Target width; a 200 px wide result is 48,014 bytes, exactly 3 bytes per pixel plus a 14-byte header.Values: 1–20000 |
ImageHeightinteger | Target height; with a width, the frame fits inside both.Values: 1–20000 |
ScaleProportionsboolean, default true | Set false to stretch to the exact width and height. |
ScaleIfLargerboolean, default false | Set true to never make the GIF bigger. |
ScaleImageboolean, default true | Set false to crop the top-left area rather than scale. |
ColorSpaceenum, default default | The fill goes in first, then the colors change. gray gives a one-channel P5 file of 64,015 bytes, a third of the RGB one; monochrome the same with only 0 and 255.Values: default, rgb, gray, monochrome |
FileNamestring | Output name; .pnm is added. |
StoreFileboolean, default false | Return a link that works for 3 hours. |
What carries over, and what doesn't
Kept
- Every opaque pixel of frame 1 exactly: compared with the frame flattened onto white, nothing differed
- The 400 × 160 size, unless you resize
- Your fill color exactly where the GIF was clear
Lost or not yet supported
- Transparency: a P6 file has no alpha channel, so the clear corners keep only the fill.
- Frames 2 to 4 of the sample and every timing value.
- Compression: 192,015 bytes for 64,000 pixels, against 8,956 bytes of GIF.
Limits
- One GIF per request; the result is 8-bit RGB, or one 8-bit gray channel with a gray or monochrome
ColorSpace. - Requests up to 10 MB on the Free plan or 100 MB on paid plans, all files and fields together.
- 413 for images above 100,000,000 pixels.
- Resize targets from 1 to 20,000 px.
- 504 if the conversion runs longer than 120 seconds.
Where teams use it
Input for image-processing code
Netpbm files are trivial to read in C, Python or shell pipelines: a short text header and then raw bytes. Converting GIFs to PNM saves writing a GIF decoder for a test rig or a research script.
Feeding command-line image tools
Many classic Unix image filters read netpbm. A GIF converted here goes into such a pipeline as plain P6, with no GIF decoder needed in the chain.
Questions
Is the output PPM (P6)?
Yes. The sample begins with P6, a newline and 400 160 255 (width, height, maximum value), 15 bytes before the 192,000 bytes of RGB. Gray and monochrome requests get the P5 form.
What happens to transparent pixels?
They are blended onto AlphaColor (or BackgroundColor, or white). A GIF pixel is either fully clear or fully solid, so clear pixels take the fill color exactly.
Can I get a grayscale PGM?
Yes. ColorSpace=gray returns a one-channel P5 graymap, 64,015 bytes with 7 gray levels for the card; monochrome holds only black and white.
Convert your first GIF file in a minute.