@aleburato/primeval
v0.1.1
Published
TypeScript-first Node package for primeval image approximation.
Readme
primeval
primeval turns photos and artwork into stylized reconstructions built from simple geometric shapes.
Give it an input image and it searches for a layered approximation you can export as PNG, JPG, GIF, or clean SVG output.
Inspired by Michael Fogleman's original primitive, this repository is an independent Rust implementation with a reusable core library (primeval-core), a CLI (primeval-cli), and an ESM-only Node package (@aleburato/primeval).
Highlights
- Fast hill-climbing search with multi-threaded worker contexts
- Nine shape modes in the CLI: mixed (
any), triangle, rectangle, ellipse, circle, rotated rectangle, quadratic curve, rotated ellipse, and polygon - Small working-resolution optimization with high-resolution output replay
- Vector export via SVG, plus raster output for PNG, JPG, and animated GIFs
Install
Node package
npm install @aleburato/primevalPrebuilt native addons are provided for macOS (arm64, x64), Linux (arm64, x64), and Windows (x64). Node 20+ is required.
CLI from source
Clone the repository and build the release binary:
git clone [email protected]:aleburato/primeval.git
cd primeval
cargo build --releaseOr install the CLI directly:
cargo install --path crates/primeval-cliQuick Start
Run the CLI against one of the bundled README originals:
./target/release/primeval-cli run \
docs/readme/originals/monalisa.jpg \
--output output/monalisa.png \
--emit png,svg \
--count 1000Useful options:
--shape any|triangle|rectangle|ellipse|circle|rotated-rectangle|quadratic|rotated-ellipse|polygonwithanyas the default--count <N>number of optimization steps (default100)--alpha <N>|autoshape opacity,1..255orauto(default128)--resize-input <N>working resolution (default256)--output-size <N>final replay resolution (default1024)--repeat <N>extra candidates per step (default0)--threads <N>worker thread count (defaults to available cores)--seed <N>for deterministic output--emit png,jpg,svg,gifone or more output formats
See the full CLI help with:
./target/release/primeval-cli run --helpNode Package
The npm package is ESM-only and targets Node 20+.
import { approximate } from "@aleburato/primeval";
import { readFile } from "node:fs/promises";
const input = await readFile("docs/readme/originals/monalisa.jpg");
const result = await approximate({
input: { kind: "bytes", data: input },
output: "svg",
render: {
count: 300,
shape: "any",
},
});
console.log(result.format, result.width, result.height);
console.log(result.data.slice(0, 32));Convert results to a data URI:
import { approximate, toDataUri } from "@aleburato/primeval";
import { readFile } from "node:fs/promises";
const input = await readFile("docs/readme/originals/monalisa.jpg");
const result = await approximate({
input: { kind: "bytes", data: input },
output: "png",
render: { count: 200 },
});
const uri = toDataUri(result);
console.log(uri.slice(0, 64));Abort long renders with AbortSignal:
import { AbortError, approximate } from "@aleburato/primeval";
import { readFile } from "node:fs/promises";
const controller = new AbortController();
const input = await readFile("docs/readme/originals/monalisa.jpg");
try {
const promise = approximate({
input: { kind: "bytes", data: input },
output: "svg",
render: { count: 1000 },
execution: {
signal: controller.signal,
onProgress(info) {
if (info.step === 10) {
controller.abort();
}
},
},
});
await promise;
} catch (error) {
if (error instanceof AbortError) {
console.log("render aborted");
} else {
throw error;
}
}Package notes:
- Default render options match the Rust render facade and CLI defaults:
count: 100,shape: "any",alpha: 128,repeat: 0,background: "auto",resizeInput: 256, andoutputSize: 1024. approximate()returns exactly one output format per call:svg,png,jpg, orgif.- The default shape is
any(mixed); all nine CLI shape modes are available. - Errors are mapped to
ValidationError,NotFoundError, andAbortError— useinstanceofto distinguish them. - For SVG results,
datais astring; for raster results,datais aBuffer.
Progression Gallery
Each table below shows one original image, with shape modes in rows and step counts in columns. Every preview is a JPEG thumbnail that links to the generated SVG.
Mona Lisa
American Gothic
Fiume Po (M.Kenna)
Spongebob
Benchmarks
Using docs/readme/originals/americangothic.jpg as the input image, 500 steps per run, and all nine shape modes (any, triangle, rectangle, ellipse, circle, rotated rectangle, quadratic, rotated ellipse, polygon), the Rust CLI completed the full matrix in 1m 18s versus 2m 41s for the original Go CLI from fogleman/primitive.
That works out to a 2.06x speedup overall (51.5% less total time). On this run, Rust was faster in all 9 modes and delivered 4.0% lower average RMSE overall (15.97 vs 16.63). It also produced lower RMSE in 7 of the 9 individual modes.
| Shape | Rust time | Go time | Speedup | Rust RMSE | Go RMSE | | --- | ---: | ---: | ---: | ---: | ---: | | Mixed | 7.6s | 14.6s | 1.9x | 12.3 | 13.6 | | Triangle | 4.0s | 9.1s | 2.3x | 14.4 | 14.6 | | Rectangle | 2.5s | 7.1s | 2.8x | 15.2 | 14.6 | | Ellipse | 5.6s | 18.2s | 3.3x | 12.3 | 12.6 | | Circle | 7.6s | 21.7s | 2.9x | 14.2 | 14.5 | | Rotated rectangle | 4.5s | 9.6s | 2.1x | 12.8 | 14.1 | | Quadratic | 6.1s | 23.2s | 3.8x | 39.5 | 38.3 | | Rotated ellipse | 24.8s | 39.4s | 1.6x | 11.8 | 13.8 | | Polygon | 15.2s | 17.7s | 1.2x | 11.1 | 13.7 |
Lower RMSE is better. Times are from a single local benchmark run and will vary by machine. The upstream Go CLI does not expose a fixed seed flag, so the quality comparison reflects one representative run rather than a deterministic seed-matched replay.
Usage in the Wild
Real projects using primeval beyond demos and benchmarks:
- nudaluce.com (NSFW) — my photography website uses
primeval-generated SVGs as LQIPs (low-quality image placeholders), replacing the more typical blurred-image placeholder technique with geometric previews.
Want your project listed here? Send an email to [email protected] with the URL of the related resource.
Development
Run the standard quality gates from the repository root:
# Rust
cargo fmt --check
cargo clippy --all-targets -- -D warnings
cargo test
# Node / package
npm run typecheck
npm test
npm run test:tooling
npm pack --dry-runnpm test builds the TypeScript wrapper and native addon before running the full package test suite, including the native-path tests.
For local development when you want the native build step by itself:
npm ci
npm run test:native:build
npm run test:nativeThe comparison harness lives at scripts/benchmark.py. It can compare any two compatible binaries and writes reports to output/.
License
Released under the MIT License.
