TGA to SVG API
POST a TGA to /v2/convert/tga/to/svg and get an SVG of filled paths traced from its color regions. Transparent background pixels are left out, and the tracing options control detail, colors and curves.
- Endpoint
- POST /v2/
convert/ tga/ to/ svg - Accepts
- tga
- Sample timing
- 7 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/tga/to/svg \
-H "Authorization: Bearer $PDFCONVERSIONAPI_KEY" \
-F File=@shield-icon.tga \
--form-string CurveMode=polygon \
-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/tga/to/svg",
headers={"Authorization": f"Bearer {key}"},
files=[("File", open("shield-icon.tga", "rb"))],
data={
"CurveMode": "polygon",
},
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 = "shield-icon.tga";
form.append("File", new Blob([await readFile(file)]), file);
form.append("CurveMode", "polygon");
const url = "https://api.pdfconversionapi.com/v2/convert/tga/to/svg";
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.007,
"Files": [
{
"FileName": "shield-icon.svg",
"FileExt": "svg",
"FileSize": 10330,
"FileData": "PD94bWwgdmVyc2lvbj0iMS4w…"
}
]
}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
- shield-icon.tga (16 KB)
- Output
- shield-icon.svg (10 KB)
- Source
- A 256 x 256 pixel-art shield icon with a transparent background and a half-transparent shadow, drawn by us and saved as a 32-bit run-length-encoded TGA. (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 |
|---|---|
CurveModeenum, default spline | polygon and pixel keep straight edges, which suits pixel art: the sample's 70 paths take 10,330 bytes, against 26,122 with the default curved spline.Values: spline, polygon, pixel |
FilterSpeckleinteger, default 4 | Patches covering no more than this value squared, in pixels, are merged into their neighbors. At the default 4 that is 16 px, so the 4 × 4 px rivet highlights vanish; 0 keeps them (117 paths), 20 leaves 11 paths.Values: 0–128 |
ColorPrecisioninteger, default 6 | Significant bits per color channel when grouping pixels. Lower merges similar shades: 3 left 9 fill colors on the shield.Values: 1–8 |
GradientStepinteger, default 16 | Color difference between stacked layers. 0 gave 39 paths for the shield, 64 gave 61.Values: 0–128 |
Presetenum, default none | A starting set of options. bw traces one color (46 paths); photo drops small details and smooths more (22 paths, 40,385 bytes).Values: none, bw, poster, photo |
ColorModeenum, default color | bw traces a black-and-white silhouette, the same output as Preset=bw for this sprite.Values: color, bw |
Layeringenum, default stacked | cutout writes non-overlapping shapes, 29 paths here instead of 70 stacked ones.Values: stacked, cutout |
CornerThresholdinteger, default 60 | Angles sharper than this stay corners in spline mode. 180 smooths every corner and grew the file to 60,839 bytes.Values: 0–180 (degrees) |
SegmentLengthnumber, default 4 | Target length of curve segments in spline mode; lower values follow the outline more closely.Values: 3.5–10 |
SpliceThresholdinteger, default 45 | Angle at which a spline is split into separate curves.Values: 0–180 (degrees) |
ImageWidthinteger | Resamples the TGA before tracing; the SVG's width and height follow. At 128 px the smoothing creates extra shades (22 fills).Values: 1–20000 |
ImageHeightinteger | Height to resample to before tracing.Values: 1–20000 |
FileNamestring | Base name of the .svg file. |
StoreFileboolean, default false | Keep the SVG for 3 hours and return a URL. |
What carries over, and what doesn't
Kept
- Flat colors closely: 9 of the shield's 11 visible colors appear as exact fills, and the wood came out as three fills from #935F35 to #956239 against #966034
- Every visible color exactly with
FilterSpeckle=0andCurveMode=polygon: 11 fills in 117 paths, 14,114 bytes - The transparent background: fully transparent pixels are left out, so the SVG has no backdrop
- Dimensions: the SVG's width and height are 256, one unit per source pixel
Lost or not yet supported
- Partial transparency. The shadow at alpha 112 becomes a solid #181224 path, because paths are written without opacity.
- Specks no larger than the
FilterSpecklearea, such as the 4 × 4 px rivet highlights at the default 4. - A viewBox. The root element has width and height only, so to scale it with CSS add
viewBox="0 0 256 256"yourself. - Pixel exactness: tracing approximates shapes, so use PNG when you need the original pixels.
Limits
- One TGA per request.
- Tracing is limited to 12,000,000 pixels; bigger images get a 413 that asks you to set
ImageWidth. - 100,000,000 pixels per decoded image, and requests up to 10 MB on the Free plan or 100 MB on paid plans.
- 120 seconds per conversion.
Where teams use it
Vector versions of pixel-art logos
A sprite traced with CurveMode=polygon keeps its square pixels as straight-edged paths, so it stays sharp on a large banner or in print.
Cutting and plotting from game art
Vinyl cutters and laser tools need outlines, not pixels. Preset=bw turns an icon into a single-color silhouette ready for the cutter's software.
Questions
Which settings work best for pixel art?
CurveMode=polygon keeps pixel steps as straight lines; add FilterSpeckle=0 if single highlights matter. For the shield that gave every visible color as an exact fill in 117 paths, 14,114 bytes.
Why is the half-transparent shadow solid in the SVG?
The tracer treats every pixel with alpha above 0 as opaque color, so the shadow's (24, 18, 36) is kept but drawn fully opaque. Pixels with alpha 0 are left out only when there are enough of them, as with a transparent background: a 20 × 20 clear hole in an opaque test TGA was traced as a black square. Remove or bake in soft shadows before tracing if that matters.
The SVG doesn't scale when I set its width in CSS. Why?
The file has width and height attributes but no viewBox, so browsers crop it instead of scaling. Add viewBox="0 0 256 256" (the original pixel size) to the root svg element.
Convert your first TGA file in a minute.