GetVect

Documentation

Running GetVect

Install it from the latest release (a signed, notarized dmg for macOS; an unsigned exe for Windows — see the download page for the first-launch step), or run it from source. Everything below is what the app does today.

Getting started

Clone, install, start

Not building from source? Take the dmg from the latest release — macOS on Apple Silicon, signed and notarized, with platform notes on the download section of the front page.

To build it yourself you need 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

Presets and candidate palettes rather than a wall of generic sliders, so the control you reach for matches the kind of image you are converting.

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, 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

GetVect has two network touchpoints, both in your control: AI Enhance, and a once-per-launch update check against GitHub Releases (disable with GETVECT_NO_UPDATE_CHECK=1). AI Enhance is the only one that sends anything — your image, to Google, under your own key. 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.

Updates

It tells you. It does not install.

Once per launch, GetVect asks GitHub Releases whether there is a newer version. If there is, a small dismissible banner appears in the corner with a Download link; dismiss it and that version never asks again. Nothing is downloaded in the background and nothing is installed behind your back.

On macOS that changes from v0.1.4. Squirrel — the machinery Electron apps update through — checks the replacement's signature against the running app's, and on a signed build that check passes. So the app downloads in the background and then offers Restart; choosing it exchanges the bundle and reopens on the new version. It is never installed without you asking. What it downloads is the -mac.zip named in latest-mac.yml, and it refuses to install anything whose sha512 does not match the digest in that file; both the digest and the signature on the app inside are checked by the release pipeline before publishing, and again against the asset re-downloaded afterwards.

Windows keeps notifying until there is an Authenticode certificate. An updater that pulls 117 MB and then fails at a verification step it cannot pass is worse than no updater at all.

v0.1.3 notifies on both platforms; installing itself starts with the build after it. On either platform GETVECT_NO_UPDATE_CHECK=1 stops the whole thing before any request is made — not just the banner, the check.

The check sends no identifier, asks one question, and fails silently when you are offline. To turn it off:

GETVECT_NO_UPDATE_CHECK=1 open -a GetVect
# or, permanently:
launchctl setenv GETVECT_NO_UPDATE_CHECK 1

Along with AI Enhance, that is the complete list of things this app does on a network: two touchpoints, both in your control.

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 benchmark web vectorizer's 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.

Is it signed?

macOS: yes, since v0.1.3. The dmg is signed with an Apple Developer ID and notarized by Apple, with the ticket stapled, so it opens with no Gatekeeper warning and the check works offline. Verify it yourself with spctl -a -t install -vv GetVect-0.1.3-arm64.dmg — it answers accepted / source=Notarized Developer ID — and codesign -dvvv shows Team ID 6UV93L24YL.

Windows: not yet. There is no Authenticode certificate, so SmartScreen warns on first launch. It is one-time and the steps below clear it.

What it sends: a once-per-launch check for a newer version (opt out with GETVECT_NO_UPDATE_CHECK=1), and, only if you turn it on, AI Enhance under your own API key. Nothing else leaves your machine.

"GetVect is damaged and can't be opened"

The published dmg should not produce this any more — since v0.1.3 it is signed and notarized, and spctl -a -t install -vv on it answers accepted. If you see it, check which build you have first:

spctl -a -vv /Applications/GetVect.app

A v0.1.2 or earlier download is genuinely unsigned; the fix is to get the current release rather than to work around it. A build from npm run dist is unsigned on purpose and will always do this — that one is yours, so removing the quarantine attribute is reasonable:

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

Only ever do that for a bundle you built yourself. For a downloaded one, the point of a signature is that you should not have to.

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. On Windows the engine contracts run in CI before every release; the full Electron acceptance suite is driven on macOS only.

Removing GetVect completely

macOS: drag GetVect.app out of Applications, then remove the state the app created (settings, and the encrypted AI key if you saved one):

rm -rf ~/Library/Application\ Support/GetVect
rm -rf ~/Library/Caches/getvect-updater

Windows: Settings → Apps → GetVect → Uninstall. The uninstaller removes the app, the Start Menu entry and its registry key, but leaves two things behind — measured, not guessed:

Remove-Item -Recurse "$env:APPDATA\GetVect"
Remove-Item -Recurse "$env:LOCALAPPDATA\getvect-updater"

The second one is a ~100 MB cached copy of the installer that the install itself creates and the uninstaller does not clean up. Until that is fixed, these two lines are the difference between "uninstalled" and "gone".

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 artwork 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 a shipping 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.