PDN to TGA API
POST a .pdn to /v2/convert/pdn/to/tga and get an uncompressed 32-bit TGA (image type 2) of the merged layers, alpha included. Rows are stored from the top, as the descriptor byte in the header says.
- Endpoint
- POST /v2/
convert/ pdn/ to/ tga - 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/tga \
-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/tga",
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/tga";
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.tga",
"FileExt": "tga",
"FileSize": 345618,
"FileData": "AAACAAAAAAAAAAAAaAHwACAo…"
}
]
}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.tga (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 | Texture width: 180 gave 180 × 120 in 86,418 bytes. For power-of-two textures, send both sizes with ScaleProportions=false.Values: 1–20000 |
ImageHeightinteger | Texture height, or with a width the box the sign is fitted into.Values: 1–20000 |
ScaleProportionsboolean, default true | false forces the exact box: 240 × 240 came to 230,418 bytes. |
ScaleIfLargerboolean, default false | true keeps a canvas that is already small at its own size. |
ScaleImageboolean, default true | false crops the top-left area: 180 × 90 is 64,818 bytes. |
ColorSpaceenum, default default | gray gives gray values and monochrome only black and white, in the same 32-bit layout, so the file stays 345,618 bytes.Values: default, rgb, gray, monochrome (also srgb, truecolors, grayscale); cmyk returns 400 |
TransparentColorstring | Sets alpha to 0 for one exact color. #FFDC5C, the board's yellow, left 30,185 clear pixels at the same file size.Values: #RRGGBB, R,G,B or white, black, red, green, blue, gray |
FileNamestring | Base name of the result; without it the file comes back as lemonade-stand-sign.tga. |
StoreFileboolean, default false | true gives a 3-hour link in place of base64. |
What carries over, and what doesn't
Kept
- Every merged pixel and its alpha: decoded, the TGA equals the PNG of the same sign, in both readers we tried
- Transparency: 8 alpha bits per pixel, 0 at the clear corners, with colors stored straight rather than premultiplied
- Top-left row order, flagged in the descriptor byte (0x28)
Lost or not yet supported
- Compression. Every pixel takes 4 bytes, so the sign is 345,618 bytes against 72,204 for the PNG of the same file.
- The TGA 2.0 footer, image ID and developer area.
- DPI:
ImageResolutionis accepted and not written.
Limits
- One PDN per request; merged canvas up to 100,000,000 pixels.
- 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.
- Always 32-bit and uncompressed, even for an opaque canvas; there is no 24-bit or run-length option.
Where teams use it
Game and mod textures
Some game engines and modding tools load TGA with alpha. A texture painted in Paint.NET can be exported from its .pdn by a build script rather than by hand.
Sprites cut out by a key color
Art drawn over a flat key color can be sent with TransparentColor to get a TGA whose key color is clear in the alpha channel.
Questions
Is the TGA compressed?
No. The route writes uncompressed TGA, image type 2: an 18-byte header plus 360 × 240 × 4 = 345,600 bytes of pixels, 345,618 in all. If size matters, the PNG of the same sign is 72,204 bytes.
Which way up are the rows stored?
Top row first. The descriptor byte is 0x28: bit 5 marks a top-left origin, and the low four bits say there are 8 alpha bits per pixel. A reader that ignored bit 5 would show the sign upside down; both readers we tried showed it upright.
Is the alpha premultiplied?
No. Colors are stored as they are, with alpha beside them: a nearly clear edge pixel keeps its full yellow (255, 255, 0) with alpha 1, the same values as in the merged PDN.
Convert your first PDN file in a minute.