GetVect

Documentation

Running GetVect

There is no installer yet — GetVect runs from source, or from an app bundle you package yourself. Everything below is what the app actually does today; the acceptance suite is the source of truth, and where this page and the suite disagree, the suite wins.

Getting started

Clone, install, start

Requires Node 20+ and, for now, macOS. Linux needs xvfb-run -a for the Electron tests; Windows is untested.

git clone https://github.com/craigjmidwinter/getvect.git
cd getvect
npm install     # postinstall de-quarantines + ad-hoc-signs the Electron binary
npm start       # build + launch
npm run dist    # package -> release/mac-arm64/GetVect.app (+ dmg, zip)

npm start always builds the real bundle and launches it. npm run dev runs the Vite dev server with renderer hot reload — good for iteration, and never what the test suite exercises.

npm run dist builds unsigned on purpose: that is right for running your own build and wrong for distribution. The packaging config is electron-builder.yml; the signing and notarization notes are in PUBLISH-CHECKLIST.md.

macOS note — read this before the first launch

On recent macOS, Gatekeeper/XProtect deletes the freshly-extracted, unsigned node_modules/electron/dist/Electron.app the first time you exec it. The symptom is spawn .../Electron ENOENT on a file that was there a second ago. The postinstall hook (scripts/verify-electron.mjs) detects this, strips the quarantine attributes and applies an ad-hoc signature. If you ever see that ENOENT, run npm run postinstall.

Using it

  • Drop a PNG, JPEG or BMP onto the window, or use the file picker. Anything else is refused with a message that says why.
  • Tracing starts automatically on load with the defaults — Clipart, 8 colours, Smart anti-aliasing — and runs off the UI thread with visible progress.
  • Load several images: they stack in the sidebar and selecting one switches the workspace to it. Settings are per image.
  • Change any setting and the trace re-runs. The preview you are looking at is the SVG that will be exported.

Control surface

Every control on the panel

The settings model is the real product's, captured live rather than guessed: model presets and candidate palettes, not detail/smoothing/despeckle sliders.

Model presets

PresetWhat it is forExtra control
Clipart Flat-colour artwork — stickers, decals, logos, t-shirt and tattoo art. The default, and the case the whole engine is tuned for. Detail Level: maximum, ultra, very-high, high, medium, low, minimum
Photo Continuous-tone images. Deliberately not the target use case — it works, but the quality bars are looser for a reason. Uses a colour floor of 16, so a lower colour count will not be honoured
Sketch Grayscale line work.
Drawing Pure black and white, split on luminance. Threshold 0–255. Colour controls are disabled here, because the preset cannot use them.

Input palette

  • Candidate palettes are computed from the image and offered at eleven fixed sizes — 1, 2, 3, 4, 5, 6, 8, 12, 15, 16, 18 — as selectable rows, so you pick a palette you can see rather than a number.
  • The palette editor can recolour a swatch, merge two swatches, or remove one. Recolouring repaints regions and leaves the geometry byte-identical; an edit is matched against the engine's colour slots, so a merge survives the next setting change.
  • Restore puts back the computed palette and the candidate size your edit replaced.
  • The colour-count hint says whether a shortfall came from the image (it does not contain that many colours) or from your settings (a cleanup pass merged some) — it will not blame the picture for the app's own fold.
The input palette panel: candidate palette sizes listed as selectable rows of swatches, with the palette editor open below showing individual colours that can be recoloured, merged or removed
Candidate palettes at eleven fixed sizes, plus the swatch editor.

Output colour groups

  • Per-colour toggle — switch a colour off and it is omitted from the output entirely. Switching off the background colour is how you get a transparent background; there is no separate transparency mode.
  • Merge threshold — percent coverage below which a colour group folds into its neighbour. Set it to 0 to turn the fold off completely (it is your control, and nothing else raises it behind your back).
  • Sort order — coverage, brightness or hue. This is the order the layers appear in, in the panel and in the exported SVG.

Quality

ControlValuesWhat it does
Enhance (Beta) off / on A local denoise and colour-simplification bundle inside the engine. Raises the area floors so quantization debris loses its scraps on the picture rather than its seat in the palette. Entirely offline — not to be confused with AI Enhance below.
Noise Reduction off / low / high Pre-trace smoothing for speckled sources.
Anti-aliasing off / smart / mid Smart is the default, as it is in the real product, and it is the single highest -value setting here: a pre-trace edge cleanup worth 1747 sub-paths → 551 and 396 KB → 140 KB on the reference artwork. It is the difference between output that looks traced and output that looks drawn.

Advanced vectorization

ControlValuesWhat it does
Roundness3 levels Curve-fitting aggressiveness — how hard the tracer pushes polylines into cubics.
Minimum Area0 / 5 / 90 px² Speck removal. Shapes smaller than the threshold are dropped; 0 keeps every speck.
Overlapfull / high Whether lower colour layers are painted under the upper ones or trimmed to their visible area.
Circle Detectionoff / on Recognises near-circular contours and emits them as clean circles.
Result stylefilled / stroked Filled colour layers, or stroked outlines of the same geometry.

Preview

  • Original/vector toggle and side-by-side mode.
  • Zoom in, out and fit, plus pan — synchronised across both panes, so the crop you are judging is the same crop in both.
  • The vector pane is the exported SVG itself, not a re-render of it. That is enforced by a test that exports through the UI and compares it byte-for-byte with the engine run headlessly on the same file.
Both preview panes zoomed to the same crop: the source raster on the left showing speckle and stair-stepped edges, the traced vector on the right showing clean flat colour and smooth outlines
Synchronised zoom. The speckle on the left is gone from the trace on the right, and the edges stay crisp at any magnification.

Export

Five formats, through the native save dialog

Every export goes through the real macOS save dialog with a sensible default filename derived from the image. Nothing is written anywhere else.

FormatWhat you get
SVG One <g fill="rgb(r,g,b)"> group per colour, so the file drops into Illustrator or Inkscape as editable colour groups. viewBox="0 0 w h" in source pixels. A disabled background colour simply is not in the file — there is no full-bleed rectangle faking transparency.
DXF — splines The default. R2000, with every fitted cubic travelling as a degree-3 SPLINE, so a CAD or cutter file is a drawing rather than a point cloud.
DXF — lines The Splines / Lines picker beside the DXF button flattens everything to R12 POLYLINE/VERTEX for older CAD and cutter firmware that cannot read a spline at all. Same drawing, same extents, no curves.
EPS PostScript paths with a correct bounding box; curves survive as curve operators.
PDF A single page with a MediaBox at the source dimensions.
PNG A raster re-render of the traced result — useful for a quick check that the cleanup did what you wanted.

A DXF that flattens every fitted cubic into vertex runs is roughly 12× the EPS of the same drawing; the spline variant lands at about 2.2×. If your machine can read splines, keep the default.

AI Enhance

Optional, off by default, bring your own key

AI Enhance is the only thing in GetVect that can touch the network. Everything else runs under a default-src 'self' content security policy with no runtime network calls at all.

The trade, stated plainly

With AI Enhance on, the image you are working on is uploaded to Google under your own API key and their terms — which is exactly the thing the rest of this app exists to avoid. Nothing else changes: no other image, no other feature, no telemetry, and the switch is off until you turn it on.

Setup

  • Get a key from Google AI Studio.
  • Paste it into the AI Enhance group in the settings panel and press Save key. The status beside it reads "Key saved" — a boolean, never the value.
  • Switch on Enhance with AI before tracing. The toggle is inert until a key exists.
  • Clear deletes the stored key from the machine.

Quality tiers

TierModelTrade
Fast (default) gemini-2.5-flash-image About 8 s, cheaper, leaves some soft texture behind.
Best gemini-3-pro-image-preview About 20 s, pricier, genuinely flat fills. An A/B on the reference artwork showed the model tier — not the prompt — is what decides flatness.

How the key is handled

  • Encrypted at rest with Electron's safeStorage (Keychain on macOS) inside the app's data directory.
  • It lives only in the main process and is never returned to the UI. The interface can save one, clear one, and ask whether one exists — there is no IPC that hands back key material.
  • If the OS has no keystore available, GetVect refuses to store a key rather than writing it in the clear, and says so.

When a run fails

A bad key, no network, or the 60-second timeout each fall back to tracing the un-enhanced image, and the app names the reason — an authentication failure, a rate limit, a timeout. There is no state where the app hangs waiting for a model.

One thing worth knowing: Gemini returns RGB even when asked for transparency, so an enhanced image can come back flattened. GetVect never fabricates an alpha channel to cover that up.

Debugging a bad result

Set GETVECT_AI_DEBUG_DIR before launching and every successful enhance writes its exact input, its output and a small metadata file (provider, model, tier, duration, byte counts) into that directory, so a disappointing result can be inspected after the fact.

GETVECT_AI_DEBUG_DIR=/tmp/getvect-ai npm start

It is off unless the variable is set, because those are your images being written to disk.

Testing & instruments

The three layers that grade this app

npm test              # engine contract tests, then the Playwright acceptance suite
npm run test:engine   # just the engine contracts (pure Node, no Electron)
npm run instruments   # fidelity metrics -> artifacts/metrics.json
npm run screenshots   # a labelled contact sheet of the whole flow
  • Acceptance suite — Playwright driving the packaged Electron app, one spec file per reference checklist section, every test title prefixed with its id ([A1][D5]). Selectors are data-testid only.
  • Engine contractsnode --test over the pure tracing engine: determinism, setting semantics, SVG/EPS/DXF/PDF structure, alpha handling, and rasterized checks that the picture actually changed.
  • Instruments — the light meter. For every fixture it traces, rasterizes the SVG back to source dimensions and diffs: mean colour error, structural similarity, ink recall, sub-path count, speck ratio, curve ratio, stroke-width evenness, and ratios against the real vectorizer.io output of the same source.

Do not read artifacts/metrics.json whole — the stdout table is the interface, and the file is meant to be queried with jq.

Troubleshooting

When macOS gets in the way

spawn …/Electron ENOENT on a file that was just there

XProtect deleted the unsigned Electron binary out of node_modules. This is the single most common first-run failure. Re-run the repair hook:

npm run postinstall

It re-fetches if needed, strips com.apple.quarantine and applies an ad-hoc signature. An environment fix that lives in one shell session is not a fix, which is why this is a hook rather than a note.

"GetVect is damaged and can't be opened"

A packaged build from npm run dist is unsigned and not notarized. A downloaded unsigned app is quarantined, and on Apple Silicon Gatekeeper refuses it outright rather than offering the right-click → Open escape hatch. Remove the quarantine attribute yourself:

xattr -dr com.apple.quarantine /Applications/GetVect.app

Only do that for a bundle you built yourself, or one whose provenance you trust.

The Photo preset ignores my colour count

It has a colour floor of 16. Ask for 4 and you will get 10–16; the panel will not display a number the engine is not going to use.

I asked for 8 colours and got 5

Check the colour-count hint. Either the image genuinely does not contain that many distinct colours, or the merge threshold folded near-duplicates. Drag Merge threshold to 0 to turn the fold off — it is the only thing that merges colour groups, and nothing raises it behind your back.

Tests fail on Linux

Electron needs a display. Run the suite under xvfb-run -a. Windows is untested.

Going deeper

The documents this page is a summary of

Spec

REFERENCE.md

The quality bar the whole project is graded against: the acceptance checklist with ids, the gold-standard exemplar and its measured properties, and the numeric quality bar. If you want to know what "done" means here, it is this file.

Harness

docs/HARNESS.md

Every instrument, what each metric means and why it exists, the fixture manifest schema, the one-decode contract, and the engine interface contract builders have to implement.

Build log

katra/entries/

The whole build chronicled as it happened — the loop design, the ground-truth recon against the real product, the laps, and the day AI Enhance turned out to be an illustrator rather than a filter.

Contributing

CONTRIBUTING.md

Short version: the suite must be green where it was green, the instruments must not regress, and any new UI has to declare its testids in docs/TESTIDS.md.