This document describes the default path in auto-stroke-filled+potrace-default.
The central design decision is that tracing is only the final step. The hard problem is producing a foreground mask that contains the icon and not the background. Once the mask is good, Potrace can convert the mask boundary into smooth Bezier paths.
Input:
- one cropped raster icon image
- accepted as a
PIL.Image.Image - any input width/height that Pillow can load
- the crop is converted to RGB and letterboxed to 128 x 128 pixels internally
- it does not search a full screenshot for icons
Output:
html: a<span>containing an inline<svg>svg: raw SVG onlypaths: simple metadata about the generated Potrace pathsdiagnostics: selected branch, mask stats, stroke color, Potrace options, output color, visual-diff scores, and optional artifact paths
Raw SVG is the default CLI output. The returned HTML wrapper is available for web apps, site builders, and design tools that want one DOM-ready string with CSS and metadata hooks.
The output is a single-color transparent SVG. By default that color is estimated
from the recovered foreground pixels. Callers can override it with
--icon-color on the CLI or icon_color=... in the Python API. Passing
currentColor makes the inline SVG inherit color from surrounding CSS.
The stroke branch is used for line icons and outline icons.
Runtime name:
nn-gated-unet+potrace-default
Mask strategy:
nn-gated-unet
Feature channels:
- RGB channels
- alpha-like constant-color evidence
- warm and purple alpha heat bands
- warm/purple joined evidence
- spectral high-high evidence
- best spectral channel and thresholded version
- alpha flood evidence
- alpha gradient magnitude
- local dark and local light contrast
- HSV saturation and value
- Lab residual magnitude
- Lab a/b residual magnitude
- RGB residual magnitude
- normalized x/y coordinates
Model:
- compact U-Net-like encoder/decoder
- split main/aux inputs
- learned gate fuses main image channels and auxiliary evidence channels
- segmentation head plus boundary head
Why this branch exists:
Filled silhouette segmentation is not safe for all outline icons. On some cream or gold line icons, a filled-silhouette model can decide that there is no meaningful filled object and return an empty or nearly invisible mask. The stroke branch preserves those pure outlines.
The filled branch is used for filled icons and mixed fill+stroke icons.
Runtime name:
filled-silhouette-unet+potrace-default
Mask strategy:
filled-silhouette-unet
Feature channels:
- RGB channels
- grayscale
- hue encoded as sine/cosine
- saturation and value
- Lab residual from smoothed background
- Lab a/b residual
- RGB residual
- distance from border median color in Lab and RGB
- local dark and local light contrast
- edge magnitude
- normalized center distance
- normalized x/y coordinates
Post-processing:
- probability hysteresis
- median cleanup
- connected component filtering
- tiny pinhole fill
- intentional hole preservation
The hole preservation is important. Earlier cleanup filled all small internal holes, which broke map-pin centers and tag holes. The current cleanup only fills tiny raster pinholes and leaves real cutouts intact.
The adapter traces both branches when possible. Then it decides which SVG to return.
Selection signals:
- foreground area of stroke mask
- foreground area of filled mask
- filled/stroke area ratio
- visual reconstruction score after rendering SVG back onto an inpainted background estimate
Current rule:
if filled area is plausible
and filled area >= max(0.115, stroke area * 1.25)
and filled visual score <= stroke visual score + 0.012:
select filled
else:
select stroke
This rule came from testing:
- pure outline icons should usually stay stroke
- filled and hybrid icons should switch to filled when the filled branch adds a real body/silhouette
- naive union of stroke and filled masks is worse because it often adds background fragments
Both branches produce binary masks. The selected mask is preprocessed for Potrace:
- median filtering
- small component removal
- tiny white-hole filling
- thickness estimation
- option selection based on mask thickness
Potrace options are conservative:
{
"turdSize": 2,
"optTolerance": 0.18 or 0.26,
"alphaMax": 0.95 or 1.12,
}The goal is to smooth jagged mask boundaries without erasing icon features.
The mask defines the foreground pixels. The algorithm estimates a single foreground color from those pixels and injects it into the SVG paths.
If the caller passes a color override, the tracing and candidate selection stay the same, but the final SVG path fill is replaced with the requested color. The diagnostics keep both values:
strokeColor: the estimated color from the original cropoutputColor: the color actually written into the returned SVGcolorOverride: the caller-provided color, ornull
This is why the SVG does not copy the background. The background is only used for scoring and debugging.
The returned SVG contains foreground paths only. There is no background rectangle, so the icon background is transparent when rendered by a browser or SVG renderer.
This is also why the project assumes a single visual foreground color. It recovers one foreground mask and emits one output fill color. It does not preserve multicolor logos as separate SVG layers.
The selector renders the candidate SVG onto an inpainted estimate of the original background, then compares that composite against the original crop.
Metrics:
- RGB mean absolute error
- RGB RMSE
- edge difference
- high-difference pixel ratio
- dark pixel ratio delta
The current priority score is a weighted mix of those values. This is useful for choosing between candidates, but it is not a perfect truth metric. Thin stroke failures can look numerically cheap because the missing pixels occupy a small part of the crop. That is why routing rules also consider mask area.
Filled-only looked strong on hybrid icons, but it failed on some pure outline icons. The regression suite includes this behavior:
- hybrid pin route -> filled
- hybrid tag string -> filled
- pure outline cube/check/chat icons -> stroke
The default behavior is therefore not "always filled". It is an automatic selector that keeps both branches.
Union means:
final mask = stroke mask OR filled mask
It was tested and rejected as the default. It often fattens the icon or adds background artifacts from the stroke branch. The filled branch alone performed better on same-color filled/hybrid icons.