DEV Community

Cover image for Tracing AI Line Art To Vector Without Losing Line Weight: A 39-Product Postmortem

Tracing AI Line Art To Vector Without Losing Line Weight: A 39-Product Postmortem

I still have the rejection email from that Tuesday morning. Adobe Stock bounced 11 of my line art vectors in one batch, all with the same reason: the line weight did not match the preview. I had traced AI-generated line art to vector, the shapes looked right, but the strokes came out hairline-thin where the source was bold. That batch took me six hours to produce and about four minutes to get rejected. This post is the postmortem of getting the other 39 products through, and what the tracing step actually needed.

Why the AI source fights you

The line art I generate comes out of an image model as a raster PNG, usually 1024 by 1024, with anti-aliased edges. Anti-aliasing is the enemy of clean vector tracing. A stroke that looks like one confident 3-pixel line in the preview is actually a gradient of gray pixels from pure black to near-white. Every tracer I tried, including the default settings in the tools most people use, interprets that gradient as multiple nested paths. The result is a stroke that traces as two or three thin concentric outlines instead of one solid shape. On screen at 100% zoom you cannot see it. At print size, or when a reviewer zooms in, it falls apart.

The first fix cost nothing: threshold the image before tracing. I convert to grayscale and binarize at a cutoff around 128, so every pixel is either black or white. No gradient, no nested paths. This one step cut my rejections on line art from most batches to almost none.

Resolution matters more than the tracer settings

My second mistake was tracing from the 1024-pixel output directly. A 1024-pixel image traced to vector gives you paths with visible corner artifacts on anything curved. I now upscale to 2048 before binarizing, which smooths the stair-stepping without inventing detail the model never drew. Beyond 2048 I saw no improvement, only longer trace times.

On my 1 vCPU, 2 GB RAM server, that matters. Upscaling and tracing one 2048-pixel image peaks at roughly 400 to 600 MB of RSS depending on path count. That fits. Two in parallel does not, and the OOM killer picks whichever process it feels like. So the pipeline runs one image at a time, calls gc.collect() after each asset, and I check resource.getrusage(RUSAGE_SELF).ru_maxrss to watch the real ceiling rather than guessing. Heavy render steps run one file per subprocess so memory actually returns to the OS.

Stroke width is a decision, not an accident

After tracing, the paths exist but their weight is whatever the trace produced. Reviewers compare the vector against the preview. If the preview shows a bold 3-pixel stroke and the vector renders hairline, that is a mismatch rejection. If the vector is bold and the preview is thin, same problem.

So I made stroke weight an explicit parameter per product line. My florals set uses one weight, my mandala sets use another, and every file in a set matches. I set the stroke in the SVG after tracing, in points, and I render the preview from that same SVG. Preview and product come from one source of truth. Before I did this, I was rendering previews from the original PNG and submitting vectors with different weights. That was the exact failure in that first rejection email.

One more rule I learned the hard way: expand strokes before export. Adobe and Vecteezy both handle stroked paths inconsistently depending on how their preview pipeline rasterizes them. Outlined strokes render the same everywhere. It bloats file size a bit, my average SVG went from about 40 KB to 90 KB, but zero of my outlined-stroke files have been bounced for rendering differences.

The metadata and the ledger

Getting vectors accepted was only half the work. The other half was not fumbling the upload. All three of my channels reject a CSV with the wrong header instantly: Adobe wants 5 columns, Vecteezy 4, Dreamstime 15. I wrote make_metadata.py to emit exactly those headers from a folder of files, and I stopped burning upload attempts on format errors.

The more dangerous failure was me. Running a batch twice is the fastest way to get an account flagged, and on a sleepy morning with a re-run script it is easy to do. upload_tracker.py is a plain ledger that records what I have already posted. A re-run now skips finished items instead of double-posting them. Boring tool, and it has probably saved my accounts more than the tracing fixes have.

What the numbers look like now

Across the 39 finished products, about 856 MB of packaged output, the tracing step is down to roughly two minutes per image on that same 1 vCPU box, single process, no GPU. Rejection rate on line art batches went from most of the batch to a handful of files per hundred. The parent pipeline behind it is around 19,000 lines of Python, and the rules in this post are the ones enforced in code, not the ones I keep in my head.

Everything here runs on Python 3.9 with two third-party packages total. No cloud account, no GPU, no framework.

The tool built from this

I did not set out to write a kit. I set out to ship 39 products, and these scripts are what was left over: make_metadata.py for the three CSV formats, upload_tracker.py for the ledger, md2pdf.py for turning docs into sellable PDFs, and pack_product.py for zipping files with a 1280x720 cover and a 600x600 thumbnail. They are the same ones I run today. If you want them as-is instead of rebuilding from this post, they are here:

See the kit


Building digital products? Free guides & kits for digital product sellers.

Top comments (0)