npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@slithy/prim-lib

v0.5.1

Published

Core engine for primitive-based image reconstruction.

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 — wraps HTMLCanvasElement or OffscreenCanvas (environment-detected); handles image loading, pixel reads, drawing steps, and SVG output
  • Optimizer — runs the step loop; calls onStep after each shape is placed. Accepts an optional schedule function (defaults to requestAnimationFrame; pass fn => setTimeout(fn, 0) for worker contexts)
  • State — holds the current canvas and its distance from the target
  • Step — a single candidate shape placement; computes best color and difference change

Shapes

Fixed shapes (class constructors, pass directly in shapeTypes):

  • Triangle, Rectangle, Ellipse, Circle, Square, Hexagon, Glyph, Debug

Configurable shapes (factory functions that return a constructor):

  • makeNGon(opts) — N-sided polygon; supports regular and irregular modes
  • makeRect(opts) — axis-aligned or rotatable rectangle with independent width/height control
shapeTypes: [Triangle, makeNGon({ sides: 6, regular: true }), makeRect({ aspectRatio: 1.78 })]

Factory options

NGonOptions

interface NGonOptions {
  sides: number               // number of vertices (≥ 3, required)
  regular?: boolean           // true = equidistant vertices (default: true)
  rotatable?: boolean         // regular only: random rotation each instance (default: true)
  startAngle?: number         // regular, non-rotatable only: fixed orientation in radians
                              // default: -π/2 (top vertex pointing up)
  noise?: number              // regular only: 0–1 vertex jitter (default: 0)
  convex?: boolean            // irregular only: apply convex hull (default: false)
  sizeRange?: [number, number] // vertex radius range in compute-space px (default: [1, 20])
                               // regular: radius from center; irregular: scatter radius from first point
  mutationScale?: number      // max mutation step size in px (default: 20)
}

RectOptions

interface RectOptions {
  widthRange?: [number, number]   // half-width range in compute-space px (default: [5, 40])
  heightRange?: [number, number]  // half-height range in compute-space px (default: [5, 40])
                                  // ignored when aspectRatio is set
  aspectRatio?: number            // width ÷ height; locks proportions, derives hh from hw
                                  // e.g. 1.78 for 16:9, 1.33 for 4:3, 1.0 for square
  rotatable?: boolean             // random rotation per instance (default: false)
  mutationScale?: number          // max mutation step size in px (default: 20)
}

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>
  shapeWeights?: number[]  // per-shape selection weights (same length as shapeTypes)
  fill: 'auto' | string
}
  • PreCfgCfg with optional width/height; used before image dimensions are known
  • ShapeInterface — structural interface for shapes; includes toData(alpha, color): StepData
  • Ctx2DCanvasRenderingContext2D | OffscreenCanvasRenderingContext2D; used for canvas operations that work in both main-thread and worker contexts
  • Bbox, 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: 'p';  a: number; c: RGB; pts: [number, number][] }       // makeNGon (regular and irregular)
  | { t: 'e';  a: number; c: RGB; cx: number; cy: number; rx: number; ry: number } // Ellipse
  | { t: 'c';  a: number; c: RGB; cx: number; cy: number; r: number }              // Circle
  | { 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 } // Glyph
  | { t: 'rc'; a: number; c: RGB; cx: number; cy: number; hw: number; hh: number; angle: number } // makeRect

SerializedOutput — 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 | OffscreenCanvas
  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.

renderStepToCtx(data: StepData, ctx: Ctx2D): void — renders a single StepData onto a 2D canvas context. Used internally by replayOutput and by runWorker to incrementally apply worker-posted steps to the display canvas.

stepDataToSVGElement(data: StepData): SVGElement — creates a DOM SVGElement from a StepData. Used internally by replayOutput and by runWorker to build the live SVG incrementally on the main thread.

Utilities

  • getFill(data: ImageDataLike): string — computes a fill color (average of corner pixels) from image data
  • parseColor(str: string): RGB — parses "rgb(r, g, b)" to an [r, g, b] tuple
  • stepPerf — 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: true on 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-lib reuses 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 for getImageData are created with this flag, keeping pixel data in CPU memory and suppressing browser warnings
  • Worker-compatibleCanvas detects its environment: in a browser context it creates HTMLCanvasElement; in a worker it creates OffscreenCanvas (same 2D API). Canvas.fromBitmap() loads an image from an ImageBitmap (worker-compatible) rather than new Image(). Optimizer accepts an injectable schedule function so callers can substitute setTimeout for requestAnimationFrame in worker contexts. The original included importScripts()-based worker stubs (not ESM-compatible) which were removed; worker support is now done properly via prim-interface's runWorker()
  • Architecture split — the original is a single-layer library. Here, prim-lib is the pure algorithm (no knowledge of how images arrive or where results go), and prim-interface is the adapter that wires it to the browser
  • PreCfg / Cfg distinctionCanvas.original() accepts PreCfg (optional width/height) and resolves to a fully-populated Cfg, making the config lifecycle explicit in the types
  • SerializationStepData / SerializedOutput allow completed runs to be stored compactly and replayed via replayOutput() without re-running the optimizer
  • Additional shapesCircle, Square, Hexagon, and Glyph are not in the original; Circle is a uniform-radius variant of Ellipse; makeNGon and makeRect are configurable factory shapes also not in the original
  • Shape weightingshapeWeights allows biased shape selection with a guaranteed distribution: the optimizer pre-allocates exact per-shape step counts (largest-remainder rounding) and shuffles them, so the final mix always matches the requested weights

Architecture notes

  • Shape.rasterize() reuses a single module-level canvas (_rasterCanvas) grown to the max shape size seen; in a worker, this is an OffscreenCanvas (environment-detected by the Canvas constructor). Avoids per-call canvas creation which was the dominant cost (27x speedup)
  • Cfg.shapeTypes holds shape constructors; shapes are chosen randomly each step unless shapeWeights is set
  • When shapeWeights is provided (same length as shapeTypes), Optimizer builds a step plan at construction time: each shape type is allocated an exact number of slots proportional to its weight (using largest-remainder rounding), then the plan is Fisher-Yates shuffled. This guarantees the final shape distribution matches the weights, rather than just biasing random selection
  • PreCfg exists to bridge the gap between call time (dimensions unknown) and runtime (dimensions set by Canvas.original() or Canvas.fromBitmap())
  • Canvas.svgRoot() is a static helper shared by Canvas.empty() and replayOutput() to create the SVG root with clip path and background fill; it uses DOM APIs and is main-thread only
  • Factory shapes (makeNGon, makeRect) capture their options in a closure and return an inner class. The class carries a static _shapeSpec property ({ f: string, o: opts }) used by runWorker to serialize and reconstruct the factory call inside the worker