A batch of 60 line art assets came back from Adobe Stock review in one afternoon. Eleven of them failed QC with the same note: visible gray pixels along the stroke edges. Same prompt set, same exporter, same threshold value I had been using for weeks. I opened one of the rejected files at 800% zoom and there it was: a fuzzy halo of near-black pixels around every line, exactly the kind of thing a reviewer flags as "not clean vector-ready art". That afternoon cost me a week of rework and taught me that binarisation is not a detail. It is the step that decides whether your batch survives review.
Here is what I learned, with the actual numbers.
Why AI line art arrives dirty
Diffusion models do not output lines. They output probability fields that look like lines. What lands in your PNG is a stroke whose core sits around RGB 20-40 and whose edges fade through 80, 120, 160, all the way into the background. To your eye at 100% zoom it reads as clean black on white. To a stock reviewer inspecting at high magnification, it reads as soft, low-contrast edges, and several platforms treat that as a quality defect.
Adobe Stock's review for illustrations checks technical cleanliness: artifacts, noise, and edges that look machine-degraded. Vecteezy and Dreamstime do the same in their own review passes. A gray halo is the single most common artifact in AI line art, and it is also the easiest one to remove, if you pick the right threshold.
The threshold that actually works
I tested three fixed values across 200 assets and logged the rejection rate for each:
- Threshold at 100 (on a 0-255 scale): strokes stay slightly thin, faint details vanish. Around 14% of the batch lost small features like whisker lines and inner detail strokes.
- Threshold at 160: the gray halo survives. This is where my 11 rejections came from. The halo pixels sit in the 120-155 band, and a 160 threshold keeps them.
- Threshold at 128, the "obvious" midpoint: better, but the halo still leaks through on softer renders, roughly one file in ten.
What worked was not a single magic number. It was a two-step pass:
- Autocontrast or normalize the grayscale image first, so the background pushes to pure 255 and the stroke core pushes toward 0.
- Then binarise at 128. After normalization, the halo pixels compress into a narrow band and 128 cuts cleanly through the middle of it.
On the re-run of the rejected 11, all passed. On the next 300 assets, zero edge-related rejections.
Morphology, because thresholding alone is not enough
Even a clean threshold leaves two problems: single stray pixels in the background (dust from the diffusion noise) and pinholes inside thick strokes. Both show up in review. Two Pillow-adjacent operations fix them, and I do them in this order:
- A median filter or a "remove small specks" pass first, to kill isolated background pixels.
- Then a despeckle on the inverted image to close pinholes inside strokes.
The order matters. Despeckling before you remove dust can weld dust specks to nearby strokes and create small blobs, which is worse than either problem alone.
One more habit: inspect at low resolution, not high. Rendering a 4000 px preview to check for halos wastes RAM and tells you less than a 600 px thumbnail, where any remaining gray band shows up as visible muddiness. On my 1 vCPU, 2 GB box, dropping inspection renders from 300 dpi to 60 dpi cut peak memory per file enough that I stopped hitting swap entirely. I confirmed the ceiling with resource.getrusage(RUSAGE_SELF).ru_maxrss rather than guessing, and processed one file per subprocess so heavy renders returned memory to the OS between assets.
JPEG is a trap for binary art
Even after a perfect threshold, saving to JPEG at default quality re-introduces ringing artifacts around every stroke, because JPEG encodes edges with block transforms. A reviewer zooming in sees the same fuzzy halo you just removed, now added by your own export step.
The fix is boring: save line art as PNG, or if the platform demands JPEG, save at quality 95 or higher and verify the result at zoom before upload. I lost another small batch to this before I checked. The threshold was fine. The exporter undid it.
Bake the check into the pipeline, not your eyes
Manual zoom inspection does not scale. The check that replaced it is mechanical and runs in milliseconds per file:
- Count pixels in the gray band (roughly 60 to 200) after binarisation. On a clean file this is zero or near zero. If it is above a small threshold, the binarisation failed and the file gets flagged for a retry at a different threshold.
- Verify the histogram is genuinely bimodal: two peaks, near-black and near-white, almost nothing between.
That automated check is what let me run overnight batches on the little server with confidence. The pipeline ran one process at a time, called gc.collect() after each asset, and any file that failed the gray-band check went into a retry queue instead of into the upload folder. The upload ledger then made sure nothing got submitted twice, which matters, because a double-post is the fastest way to get an account flagged on these platforms.
The other lesson from that rejected batch: keep the raw outputs. Every failed file is a data point about which prompts and settings produce clean strokes. I now keep every research note and scrape log in a fixed folder structure, and I have not deleted a raw batch since. The folder layout that survived is simple: raw assets, ready-to-submit output, finished packs, and research, each in its own directory.
Where this lives now
Everything above is baked into the tooling I use daily. The Pipeline Starter Kit contains the four scripts from that pipeline: a metadata builder that writes the exact Adobe 5-column, Vecteezy 4-column, and Dreamstime 15-column CSV headers those platforms reject you for getting wrong, an upload tracker that prevents double-posting, a Markdown to PDF converter, and a packager that produces product zips with a 1280x720 cover and 600x600 thumbnail. Two dependencies total, standard library only for the metadata and tracking tools, and the whole thing validated on 1 vCPU and 2 GB RAM with no GPU. It packages 39 products and 856 MB of output before I shipped it.
If you are building a batch pipeline for line art or any digital asset, this is the packaging layer I wish I had on day one.
Building digital products? Free guides & kits for digital product sellers.
Top comments (0)