@slithy/prim-lib
v0.3.2
Published
Core engine for primitive-based image reconstruction.
Maintainers
Readme
@slithy/prim-lib
Core engine for primitive-based image reconstruction. Consumed by @slithy/prim-interface.
What it does
Reconstructs an image by iteratively placing geometric shapes. Each step evaluates candidate shapes, mutates them, picks the one that most reduces the difference from the target, and draws it onto the working canvas.
Exports
Classes
Canvas— wrapsHTMLCanvasElement; handles image loading, pixel reads, drawing steps, and SVG outputOptimizer— runs the rAF loop; callsonStepafter each shape is placedState— holds the current canvas and its distance from the targetStep— a single candidate shape placement; computes best color and difference change
Shapes
Shape— abstract baseTriangle,Rectangle,Ellipse,Square,Hexagon,Glyph,Debug
Types
Cfg — runtime config (all fields required):
interface Cfg {
width: number // image width for computation (set by Canvas.original)
height: number // image height for computation (set by Canvas.original)
steps: number // total shapes to place
shapes: number // candidate shapes evaluated per step
mutations: number // hill-climb attempts per candidate
alpha: number // starting shape opacity
mutateAlpha: boolean // whether opacity is mutated per candidate
computeSize: number // max image dimension for pixel distance calculations
viewSize: number // max image dimension for display canvas
allowUpscale?: boolean // allow output larger than source image (default: false)
scale?: number // viewSize / computeSize ratio (set by Canvas.original)
shapeTypes: Array<new (w: number, h: number) => ShapeInterface>
fill: 'auto' | string
}PreCfg—Cfgwith optionalwidth/height; used before image dimensions are knownShapeInterface— structural interface for shapes; includestoData(alpha, color): StepDataBbox,Point,ImageDataLike,ShapeImageData
RGB — [number, number, number] tuple
StepData — discriminated union of serialized shape data, keyed by t:
type StepData =
| { t: 't'; a: number; c: RGB; pts: [number, number][] } // Triangle
| { t: 'r'; a: number; c: RGB; pts: [number, number][] } // Rectangle
| { t: 'e'; a: number; c: RGB; cx: number; cy: number; rx: number; ry: number } // Ellipse
| { t: 's'; a: number; c: RGB; cx: number; cy: number; r: number } // Square
| { t: 'h'; a: number; c: RGB; cx: number; cy: number; r: number; angle: number } // Hexagon
| { t: 'sm'; a: number; c: RGB; cx: number; cy: number; fs: number; text: string } // GlyphSerializedOutput — compact, storage-ready representation of a completed run:
interface SerializedOutput {
v: 1 // schema version
w: number // compute width
h: number // compute height
scale: number // viewSize / computeSize ratio
fill: RGB // background fill color
steps: StepData[]
}ReplayResult — returned by replayOutput:
interface ReplayResult {
raster: HTMLCanvasElement
svg: SVGSVGElement
svgString: string
}Functions
replayOutput(data: SerializedOutput): ReplayResult — reconstructs canvas and SVG natively from serialized output, without re-running the optimizer. Useful for restoring saved results from localStorage or a database.
Utilities
getFill(data: ImageDataLike): string— computes a fill color (average of corner pixels) from image dataparseColor(str: string): RGB— parses"rgb(r, g, b)"to an[r, g, b]tuplestepPerf— accumulator for profiling rasterize vs pixel math time per step
Differences from primitive.js
prim-lib is derived from primitive.js, a JavaScript port of the Go primitive library by Michael Fogleman. The core algorithm — iterative shape placement via hill-climbing — is unchanged. What's different:
- TypeScript, strict mode — fully typed throughout;
strict: trueon both packages - ESM only — no CommonJS; no
importScripts() - Canvas reuse — the original creates a new
<canvas>element for every shape rasterization call (~58,000 per run at default settings).prim-libreuses a single module-level canvas, growing it only when a larger bbox is seen. This delivered a ~27× speedup (37 s → ~0.8 s rasterize time per run) willReadFrequently: true— canvas contexts used forgetImageDataare created with this flag, keeping pixel data in CPU memory and suppressing browser warnings- Workers removed — the original included worker stubs (
importScripts()-based, not ESM-compatible). Workers were investigated and benchmarked; after the canvas reuse optimization, a batched worker implementation matched single-threaded performance. Workers are not used - Architecture split — the original is a single-layer library. Here,
prim-libis the pure algorithm (no knowledge of how images arrive or where results go), andprim-interfaceis the adapter that wires it to the browser PreCfg/Cfgdistinction —Canvas.original()acceptsPreCfg(optionalwidth/height) and resolves to a fully-populatedCfg, making the config lifecycle explicit in the types- Serialization —
StepData/SerializedOutputallow completed runs to be stored compactly and replayed viareplayOutput()without re-running the optimizer
Architecture notes
Shape.rasterize()reuses a single module-level canvas (_rasterCanvas) grown to the max shape size seen; avoids per-callcreateElement('canvas')which was the dominant cost (27x speedup)Cfg.shapeTypesholds shape constructors; shapes are chosen randomly each stepPreCfgexists to bridge the gap between call time (dimensions unknown) and runtime (dimensions set byCanvas.original())Canvas.svgRoot()is a static helper shared byCanvas.empty()andreplayOutput()to create the SVG root with clip path and background fill
