Materializr
Open-source parametric 3D CAD for makers — constraint sketches, solid modeling, threads, SVG & text engraving, STL/STEP export. Android, iPad, Linux, Mac, Windows. Vibe coded.
Turn your images into unique hatched line art — print it, share it, or plot it with a pen plotter. Free and instant, right in your browser.
DesignSvelte · TypeScriptGitHub @dxviieOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
Turn your best memories into plotter art
Taken from the repository README
Loading the full README. The saved excerpt is shown below.
Turn any image or video into plottable, multi-pen hatched line art — entirely in your browser.Read the full README ↗Turn any image or video into plottable, multi-pen hatched line art — entirely in your browser.
RSTR splits a picture into regions of similar tone and refills each one with parallel pen strokes: dense where the image is dark, sparse where it's light. The result is a layered SVG a pen plotter can draw with real ink on real paper — or a PNG to share and print. Everything runs in your browser and your image never leaves your device. The one exception is deliberate: when you order a physical plot, the finished plot file (the drawn lines as SVG — never your photo) is uploaded into the plot queue. Asking me about a plot from the studio sends the same file along, unless you untick it.
🎨 Live app: rstr.d17e.dev · 📖 How to use it: the in-app help page documents every control · 🖋️ The art: #madewithrstr
| # | Section | What it does |
|---|---|---|
| 1 | image | Load a picture or video, crop/reposition it right on the render (drag, zoom, rotate) and tune it (brightness, contrast, gamma, saturation, vibrance) before tracing. |
| 2 | video | Frame rate and export window — shown while a video is loaded. |
| 3 | segmentation | How the image is carved into tonal regions (watershed, posterize, k-means, SLIC). |
| 4 | lines | Pen width, the ink threshold band (low/high pass), how ink intensity turns into line spacing — and the optional hand-drawn wobble. |
| 5 | presets | Randomize everything, or save and share complete looks as JSON. |
| 6 | layers | One pen per layer: color, image channel, hatch angles, per-layer overrides. |
| 7 | export | Output width — or a fixed page format (A6–A3 toggles, more sizes, custom) whose margin is masked — and the SVG / PNG / frame-sequence downloads. |
| 8 | stats | Render numbers and the estimated plot time. |
This README covers how RSTR is built. For what each setting does, see the help page (
src/routes/(site)/help/+page.svelte) — it mirrors the in-app tooltips.
RSTR is a fully client-side image pipeline. An image (or a single video frame) flows through a chain of stages, each one a self-contained function over flat typed arrays:
image → crop/reposition view (drag · zoom · rotate on the render)
→ optional output format (fixed page frame, margin masked)
→ pixels → cell grid → color adjust → per-layer channel extract
→ segmentation (watershed | posterize | k-means | SLIC)
→ region geometry (contour tracing, holes included)
→ hatching (ink-driven line spacing per region)
→ optional hand-drawn wobble (deterministic per-line value noise)
→ preview (canvas) + export (SVG per pen / PNG / frame-sequence zip)
Each stage is wired to Svelte 5 runes as an independent $effect with its own dependency set, so a slider only recomputes its own stage and everything downstream — dragging a pen-width slider never re-runs the watershed. Long stages yield to the frame loop and abandon stale generations, which keeps the sliders responsive on large grids.
Segmentation is where the character comes from. All four algorithms work on the same cell grid and share the same post-processing (tolerance-based region merging, small-region absorption, compact relabeling), so they're interchangeable:
(x, y, intensity) that carves the grid into compact, roughly cell-sized superpixels, then merges similar neighbours. Mosaic-like.Layers are the pen model. A layer is just data: an image channel (cyan / magenta / yellow / key / R / G / B / luminance — or "match pen color", which hue-selectively separates against the layer's own ink color) that drives its ink amount, plus a color, a hatch-angle range, and optional per-layer overrides of the global line settings. Each layer maps to one physical pen and becomes one <g> in the exported SVG. CMY is only the default stack.
Hatching fills each region's contours (holes included) with parallel lines whose spacing is driven by the region's mean ink, through a configurable gamma/boost curve. An ink threshold band acts as a low/high pass filter: regions below its low bound (too faint) or above its high bound (too dense) are left empty, so a band narrowed from both ends isolates the midtones. Each region picks its own angle within the layer's range based on its shape, so a single pen never looks mechanical.
Hand-drawn wobble (off by default) post-processes the straight hatch segments into polylines that meander like real pen strokes: two octaves of value noise displace interior points perpendicular to the ideal line, tapered to zero at the (exactly preserved) endpoints, with per-line "pressure" variance. Three controls shape it — squiggle (amplitude, mm), wave (wavelength, mm) and a variation seed. The wobble is a pure function of a line's endpoints and the seed, so preview, exports and the plot-time estimate always agree — and when it's off, that code path isn't touched at all and the output stays byte-identical to the classic straight hatching.
Plot-time estimation is a duration-only port of saxi's constant-acceleration motion planner (which itself derives from fogleman/axi): per-segment cornering velocities, trapezoidal velocity profiles, pen lift/drop pauses and pen-up travel. Match the plotter profile to saxi's options and the estimate lines up with what saxi reports for the exported file.
The engine (src/lib/rstr2/) is pure TypeScript with no framework imports, so it's fully unit-testable in isolation:
| File | Responsibility |
|---|---|
viewTransform.ts | The crop/reposition view: pan/zoom/rotate math behind the stage gestures, applied where source pixels are extracted. |
outputFormat.ts | Optional fixed output pages: the format catalog (A/B series, US, squares, Axi beds, custom), orientation and the masked-margin math. |
grid.ts | Downsample the source image to the working cell grid. |
imageAdjust.ts | Brightness / contrast / gamma / saturation / vibrance. |
segmentation.ts | Watershed / posterize / k-means / SLIC + shared post-processing. |
regionTools.ts | Region contour tracing (with holes). |
hatchTools.ts | Hatch-line generation and the ink→spacing curves. |
handDrawn.ts | The hand-drawn wobble: deterministic, seeded value-noise polylines over the straight hatch segments. |
layers.ts | Layer model (channel + pen mapping), defaults, persistence. |
params.ts | Global parameter defaults and localStorage persistence. |
inkColors.ts | Curated real-ink palette and color harmonies for the dice. |
randomize.ts | The one-click randomize roll and the RngProfile model behind it. |
distributions.ts · rngSources.ts · rngProfiles.ts · rngBuiltinProfiles.ts | The tunable randomness stack — sampling strategies, seeded rng streams, profile storage and the shipped-profile registry. Tuned visually in the studio's dev-only rng debug panel; see docs/rng-profiles.md. |
presets.ts | Built-in and user presets, JSON import/export. |
plotTime.ts | saxi-compatible plot-time estimation. |
svgExport.ts | Standalone SVG document assembly (one group per pen). |
video.ts · |
Most of these are backed by *.test.ts unit tests in the same folder.
Other notable areas:
src/lib/prep/ — the plot prep tool: takes an exported SVG and dresses it for the machine (output page, paper outline, calibration markers, reversed-layer doubling). reverse.ts reverses SVG path direction so a layer can be plotted twice for denser ink. It also accepts multiple same-size SVGs (a file list, a folder, or the studio's frame-sequence zip — geared to the video export): frames are laid out as a grid across as many pages as needed, same-color layers are merged for easy pen selection in saxi, each frame gets its number in single-stroke Hershey digits, and the export is a zip of page SVGs. That zip also carries a …_frame-template.svg — page 1 plus one frame-N rectangle per grid cell — which Framesheet Studio reads to turn photos of the plotted pages back into a looping animation.src/lib/rstr/ + src/lib/ccp/ — the classic engine, the original Paper.js-based RSTR, kept intact for nostalgia. Marked @ts-nocheck and intentionally left as-is.src/lib/fsm.svelte.ts — a small state machine for the render lifecycle (config → render → done / error / exporting).src/service-worker.ts — precaches the app shell so it works offline; RSTR is an installable PWA.workers/order-upload/ — the Cloudflare Worker behind ⚡ order this plot: takes the exported plot SVG (only the drawn lines — never the source image), verifies it against its design fingerprint and stores it, rate-limited, in a private R2 bucket. Studio inquiries use it too when the design goes along. Deploy and retention notes live in workers/order-upload/README.md.src/lib/rstr2/order.ts · orderForm.ts · src/lib/components/TallyEmbed.svelte — the plot funnel, built on two Tally forms: the order form (a priced design, straight to payment) and the inquiry form (a free chat about a plot, from the landing page or the studio). order.ts decides what can be ordered, prices it and builds both forms' hidden fields. TallyEmbed shows either form in a page's own dialog and falls back to a plain link when a content blocker stops the iframe. The landing page's #madewithrstr section embeds a third form the same way: the share form (src/lib/landing/share.ts), where people who'd rather not post their work send it to me directly.| Route | Purpose |
|---|---|
/ | Landing page. |
/studio | The main studio — the rstr2 engine (client-rendered only; state lives in localStorage). |
/prep | Plot prep tool. |
/classic | The original Paper.js RSTR. |
/help | Full settings & feature reference. |
/about, /landing, /v2 | Redirects (/about→/help, /landing→/, /v2→/studio) so old links keep working. |
$state / $derived / $effect) for the reactive pipeline.@tailwindcss/vite) with shadcn-svelte, clsx, tailwind-merge and tailwind-variants for styling.Requires Node ≥ 20.19 (or ≥ 22.12) — engine-strict is on. Any of npm / pnpm / bun works; both a bun.lock and a package-lock.json are committed.
# install
npm install
# dev server (add -- --open to open a browser, or npm run dev-host for LAN access)
npm run dev
# production build + local preview
npm run build
npm run preview
| Command | Does |
|---|---|
npm run dev | Start the Vite dev server. |
npm run build | Production build. |
npm run preview | Preview the production build locally. |
npm test | Run the unit tests once (Vitest). |
npm run test:unit | Run Vitest in watch mode. |
npm run check | Type-check with svelte-check. |
npm run lint | Prettier check + ESLint. |
npm run format | Format with Prettier. |
RSTR stands on a lot of other people's work:
RSTR began as a sketch for the Genuary '24 prompt "In the style of Vera Molnár" and grew from there. Made by David Vandenbogaerde (d17e.dev), a software engineer and artist in Amsterdam.
RSTR is released under the GNU General Public License v3.0. You're free to use, study, share and modify it; derivative works must stay under the GPL. Anything you create with RSTR is entirely yours — including commercial use.
zip.ts| Video-frame sampling math and a dependency-free stored-ZIP writer for frame-sequence export. |