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

@zakkster/lite-patternforge

v1.0.0

Published

Zero-GC seamless-repeat pattern kernel. Grid, halfdrop, brick, mirror, and deterministic scatter layouts + motif seam-blend + audit. Composes with lite-hueforge, lite-color-engine/remap, lite-gradient-studio.

Readme

@zakkster/lite-patternforge

Zero-GC seamless-repeat pattern kernel. Grid, halfdrop, brick, mirror, and deterministic scatter layouts. Motif seam-blend pre-processor, seamless-score audit. Built for real-time preview scrubbing and long-running design tools.

npm version sponsor Zero-GC npm bundle size npm downloads npm total downloads TypeScript Dependencies license

The tile kernel the rest of the ecosystem was missing

lite-patternforge closes the last gap in the @zakkster color + surface pipeline. Hueforge does palettes and colorway variants. lite-color-engine/remap recolors rasters. lite-gradient-studio handles seamless mesh backgrounds. Nothing between them composed a motif into a repeat pattern — grid, halfdrop, brick, mirror, or scatter. Patternforge is that piece.

npm install @zakkster/lite-patternforge
import { tileMotif, LAYOUTS } from "@zakkster/lite-patternforge";

// One motif → 4×4 halfdrop repeat, ready to blit
const motif = /* ImageData from user upload or procedural draw */;
const output = new ImageData(motif.width * 4, motif.height * 4);

tileMotif(motif, {
  tilesX: 4,
  tilesY: 4,
  layout: LAYOUTS.HALFDROP,
  offsetFrac: 0.5
}, output);

ctx.putImageData(output, 0, 0);

Seven layouts, zero allocations on the hot path, RGBA-LE Uint32Array pixel format matching Canvas ImageData and @zakkster/lite-color-engine/remap. Direct-blit compatible with everything downstream.


Table of contents


Why this exists

Surface pattern design has two problems that no library solves at once:

  1. Real-time preview scrubbing. A textile designer moving a tile-count slider expects the preview to update at 60fps. If tiling a 128² motif into a 512² repeat allocates DOM objects or new pixel buffers per frame, GC pauses show up as jitter and the tool feels laggy on anything but a fresh laptop. Patternforge tiles at native memory speed with zero allocations after warm-up.

  2. Deterministic output. When a variant explodes into 6 CVD-safe colorways × 3 layouts × 2 tile counts, that's 36 outputs — and the designer needs to be able to reproduce any specific one on demand. Every kernel here is deterministic. Scatter layouts use a seeded PRNG so { seed: 42 } today equals { seed: 42 } tomorrow, on any machine.

Existing options: canvas drawImage in a loop (allocates layer state per call, no scatter/mirror), CSS background-repeat (grid only, no export path), or roll-your-own (weeks). Patternforge is the API for this specific job.


What you get

  • tileMotif(motif, opts, out) — ImageData-shaped convenience. Dispatches on opts.layout. The API most callers reach for.
  • Seven flat-array kernels for zero-alloc composition:
    • tileMotifU32 — grid (straight repeat)
    • tileMotifHalfdropU32 — offset alternating columns
    • tileMotifBrickU32 — offset alternating row-bands
    • tileMotifMirrorXU32 — H-flip alternating columns (book-match)
    • tileMotifMirrorYU32 — V-flip alternating row-bands
    • tileMotifMirrorXYU32 — kaleidoscopic 2×2 book-match
    • tileMotifScatterU32 — deterministic per-tile rotation + optional flips
  • makeMotifSeamlessU32 — crossfade opposite edges of a motif so it grid-tiles without visible seams. Cosine-eased, symmetric.
  • seamlessScore — audit function returning RMS RGB discontinuity across horizontal, vertical, and overall seams. Use it to decide whether a motif needs seam-blending before grid tiling, or whether mirror is a better choice.
  • LAYOUTS — frozen enum of layout string constants for autocomplete and typo protection.

Full types ship in Patternforge.d.ts. Every export has JSDoc.


The seven layouts

Notation: tile positions are indexed (tx, ty) starting at (0, 0) in the top-left; motif coordinates within each tile are (mx, my).

Grid

The straight repeat. Every tile is an identical copy of the motif. The most common layout; the baseline everything else compares against.

Every seam is a hard cut. If your motif's right edge doesn't naturally match its left edge, grid layout will show it.

Halfdrop

Odd tile columns (tx = 1, 3, 5, ...) are offset vertically by motifH × offsetFrac pixels. With the default offsetFrac = 0.5, adjacent columns are shifted by exactly half the motif height. Column 0's motif row k appears at column 1's row (k + motifH/2) mod motifH.

Common in textile design (bricks-on-edge, scales, hexagonal-looking tessellations). Hides straight vertical seams that grid exposes.

Brick

Odd tile row-bands (ty = 1, 3, 5, ...) are offset horizontally by motifW × offsetFrac pixels, shifted right with wrap-around. Classic masonry pattern. Hides horizontal seams the same way halfdrop hides vertical ones.

Mirror-X (book-match, horizontal)

Odd tile columns are the motif flipped horizontally. Adjacent columns become mirror images, so the seam between them contains identical pixel columns twice — visually invisible. Excellent for photographic motifs.

Mirror-Y (book-match, vertical)

Same idea, other axis. Odd row-bands are the motif flipped vertically.

Mirror-XY (kaleidoscopic 2×2 book-match)

Both axes. Each 2×2 tile block reads as the motif plus its H-flip, V-flip, and HV-flip variants. Every seam invisible. Most forgiving for photographic and highly-directional motifs.

Scatter (deterministic random)

Grid-aligned tile placement, but each tile picks one transform variant deterministically from opts.rotations × optional flipH × optional flipV, using a seeded PRNG. Same seed + same grid → same output, on any machine, forever.

Rotations at 90° / 270° swap tile dimensions and therefore require a square motif. For non-square motifs, restrict rotations to [0, 180].

Composed with flipH: true and flipV: true, scatter with all four rotations produces 16 distinct variants, distributed across tile positions by the hash.


API reference

Flat-array kernels

All seven layout kernels share the same call shape:

kernel(motifU32, motifW, motifH, outU32, tilesX, tilesY, [layoutOpts])
  • motifU32 — Uint32Array of length ≥ motifW × motifH, RGBA-LE encoded (R = value & 0xFF, G = (value >> 8) & 0xFF, B = (value >> 16) & 0xFF, A = (value >> 24) & 0xFF). Any Uint8ClampedArray from a Canvas ImageData can be viewed as Uint32Array at zero cost.
  • outU32 — Uint32Array of length ≥ motifW × motifH × tilesX × tilesY. Written in place.
  • tilesX, tilesY — positive integers.
  • Layout-specific options come last: offsetFrac for halfdrop/brick, a ScatterOptions object for scatter.

Every kernel validates its inputs (throws TypeError on wrong types, RangeError on non-positive or undersized dimensions). Validation happens once up front; the hot loop assumes correctness.

tileMotifU32(motifU32, motifW, motifH, outU32, tilesX, tilesY)

Grid layout. Row-by-row cache-friendly copy loop.

tileMotifHalfdropU32(motifU32, motifW, motifH, outU32, tilesX, tilesY, offsetFrac?)

Halfdrop layout. offsetFrac defaults to 0.5. Any finite number accepted (wrapped mod 1); NaN/Infinity throw RangeError. offsetFrac = 0 naturally falls back to grid behavior — no special case.

tileMotifBrickU32(motifU32, motifW, motifH, outU32, tilesX, tilesY, offsetFrac?)

Brick layout. Same offsetFrac conventions as halfdrop. Shifts right with wrap-around.

tileMotifMirrorXU32(...) / tileMotifMirrorYU32(...) / tileMotifMirrorXYU32(...)

Mirror layouts. Same shared signature, no extra options.

tileMotifScatterU32(motifU32, motifW, motifH, outU32, tilesX, tilesY, opts?)

interface ScatterOptions {
    rotations?: (0 | 90 | 180 | 270)[];   // default [0, 180]
    flipH?: boolean;                       // default false
    flipV?: boolean;                       // default false
    seed?: number;                         // default 0
}

Rotations of 90° or 270° swap tile dimensions and require a square motif — throws RangeError otherwise. Rotations [0, 180] work on any dimensions.

ImageData wrapper

tileMotif(motif, opts, out)

ImageData-shaped convenience. Dispatches on opts.layout (default 'grid'). Options object accepts everything the underlying kernel accepts, plus tilesX and tilesY.

interface TileMotifOptions extends ScatterOptions {
    tilesX?: number;         // default 1
    tilesY?: number;         // default 1
    layout?: PatternLayout;  // default 'grid'
    offsetFrac?: number;     // for halfdrop / brick
}

Output-dimension contract is strict: out.width must equal motif.width × tilesX and out.height must equal motif.height × tilesY. Throws RangeError on mismatch. This is deliberate — silent truncation would hide grid-math bugs.

Motif pre-processor

makeMotifSeamlessU32(inU32, motifW, motifH, outU32, bandFrac?)

Cross-blend a motif's opposing edges so the result grid-tiles seamlessly. Fades the right band toward the left edge, bottom band toward the top edge, using a cosine ease. At the exact seam positions, output is a 50/50 blend of both edges — grid tiling shows no boundary.

  • bandFrac ∈ (0, 0.5]. Default 0.1 (10% of each dimension). Larger bands are softer but blur more of the motif. Smaller are sharper but less forgiving.
  • inU32 is not modified; outU32 receives the seamless variant.
  • Zero allocation on the hot path.

The mirror kernels (Mirror-X, Mirror-Y, Mirror-XY) achieve seamlessness a different way (reflection) and don't need this pre-processor.

Corner artifacts: horizontal and vertical blending are applied sequentially, so the four corners get double-blended. This introduces a very slight asymmetry that's acceptable for photographic motifs and typically imperceptible below bandFrac = 0.15. Documented and honest — for perfect corners, use mirror-xy instead.

Audit

seamlessScore(motifU32, motifW, motifH) → { horizontal, vertical, overall }

Returns three scores in [0, 1] where lower is better (0 = perfectly seamless, 1 = maximum discontinuity):

  • horizontal — RMS RGB discontinuity across the horizontal seam (right edge vs left edge, position-matched by row).
  • vertical — RMS RGB discontinuity across the vertical seam.
  • overall — Euclidean combination √(h² + v²) / √2.

Alpha channel ignored. Empirically calibrated thresholds:

| Score | Interpretation | | ----------- | ------------------------------------------ | | < 0.02 | imperceptible | | 0.02 – 0.05 | visible on close inspection | | 0.05 – 0.15 | obvious seam | | > 0.15 | disruptive; needs seam blend or mirror |

Use it to gate whether your workflow should reach for makeMotifSeamlessU32 or switch to a mirror layout.


Composability with the ecosystem

The whole pipeline, end to end:

import { extractPaletteWithWeights, createColorways } from '@zakkster/lite-hueforge';
import { remapPixelsToPalette }                       from '@zakkster/lite-color-engine/remap';
import { tileMotif, seamlessScore, LAYOUTS }          from '@zakkster/lite-patternforge';

// 1. Analyze the motif
const motif  = /* ImageData from user */;
const score  = seamlessScore(new Uint32Array(motif.data.buffer), motif.width, motif.height);
// score.overall > 0.05 → use a mirror layout or seam-blend first

// 2. Extract a palette + generate CVD-safe variants
const master   = extractPaletteWithWeights(motif, 5).map(s => s.color);
const variants = createColorways(master, { count: 6, seed: 42 });

// 3. Tile the motif into a 4×4 repeat, book-matched to hide seams
const tiled = new ImageData(motif.width * 4, motif.height * 4);
tileMotif(motif, { tilesX: 4, tilesY: 4, layout: LAYOUTS.MIRROR_XY }, tiled);

// 4. Recolor for each variant — a 6-way pattern collection
const outputs = [];
for (const palette of variants) {
    const recolored = new ImageData(tiled.width, tiled.height);
    remapPixelsToPalette(
        tiled.data,
        paletteToOklchFloat32(palette),
        new Uint32Array(recolored.data.buffer),
        tiled.width * tiled.height,
        palette.length,
        { preserveLightness: true }
    );
    outputs.push(recolored);
}
// `outputs` is the designer's 6-variant pattern collection.

Every step is deterministic. Every step is zero-GC on the hot path. Every step composes with the next through flat-array primitives — no byte swapping, no format translation, no allocation between stages.


Zero-GC design notes

Every kernel in this library allocates zero heap objects on its main loop. Validation happens once up front; the tile-copy loops afterward do nothing but integer arithmetic on typed-array indices and pixel-value transfers.

| Operation | Allocations | | ---------------------------------- | ----------- | | tileMotifU32 main loop | 0 | | tileMotifHalfdropU32 main loop | 0 | | tileMotifBrickU32 main loop | 0 | | tileMotifMirrorXU32 main loop | 0 | | tileMotifMirrorYU32 main loop | 0 | | tileMotifMirrorXYU32 main loop | 0 | | tileMotifScatterU32 main loop | 0 (per-tile hash is inline) | | makeMotifSeamlessU32 main loop | 0 (initial Uint32Array.set is native memcpy, not a JS allocation) | | seamlessScore | 1 (the returned result object) | | tileMotif wrapper | 2 (Uint32Array views over the ImageData buffers — zero-copy) |

The wrapper's two view allocations happen once per call, not per pixel — a real-time preview scrubbing use case (say, dragging a slider that changes tilesX) allocates ~2 small objects per input event. Well within V8's young-gen nursery budget; sub-millisecond collections that never surface as visible jank.

For the pure-hot-path use case (tiling in a requestAnimationFrame loop at 60fps), skip the wrapper and call the *U32 kernels directly. Then the entire pipeline allocates literally nothing per frame.

Verified by the realistic-scale test: 128² motif tiled 4×4 → 1 Mpx output, 20 iterations across every kernel, pre-allocated buffers, no observable heap growth.


Design decisions worth knowing

  • RGBA-LE Uint32 pixel convention. Uint8ClampedArray from Canvas ImageData is viewable as Uint32Array at zero cost on little-endian machines (Chrome, Firefox, Safari, Node — all little-endian). The remap kernel in @zakkster/lite-color-engine/remap outputs the same format. Direct pipeline composition, no byte swapping.
  • Strict output-size contract. tileMotif throws on dimension mismatch rather than silently truncating. Silent truncation is the enemy of deterministic graphics tools. Strict-mode forces the consumer to calculate the grid math correctly upfront, preventing invisible layout bugs downstream.
  • Halfdrop is between-tile, not within-tile. With tilesX = 1 there's only one tile column and nothing to alternate with — halfdrop degenerates to grid. This is the correct semantic (halfdrop is a tile arrangement, not a pixel transform) but occasionally surprises callers.
  • Brick shifts right. offX = round(motifW × offsetFrac); odd row-bands shift right by offX pixels with wrap-around. Direction is documented and consistent — the alternative (shift-left) produces the same visual pattern (brick is symmetric) but different pixel-exact output. Test-covered explicitly with the motifW = 6, offsetFrac = 1/3 case where left and right differ.
  • Scatter's rotation set is validated. Only [0, 90, 180, 270] are valid rotation values. Arbitrary rotation angles would require pixel resampling and are out of scope for a zero-GC kernel. 90° and 270° require a square motif; the library refuses to guess what to do with the dimension swap otherwise.
  • offsetFrac wraps mod 1. 1.5 behaves the same as 0.5, -0.25 same as 0.75. Permissive input; the only thing rejected is non-finite (NaN, Infinity).

Testing

42 deterministic tests, all pass. Ships tests for:

  • Every kernel's input validation surface (TypeError / RangeError paths)
  • Pixel-exact correctness on small motifs (2×2, 3×3, 4×4) with expected outputs computed by hand
  • Asymmetric grid shapes (3×1 tiles of 2×3 motif) to catch axis-swap bugs
  • Halfdrop's between-tile offset semantics with a full 4×4-motif 2×2-grid pixel check
  • Brick's shift direction with the motifW = 6, offsetFrac = 1/3 case that breaks left/right symmetry
  • Mirror layouts' invisible-seam property (adjacent pixels across a mirrored seam must match)
  • Mirror-XY's kaleidoscopic contract (2×2 block corners must all be the same source pixel)
  • Scatter determinism (same seed = same output) and non-determinism (different seeds diverge)
  • Scatter's dimension-swap rejection on non-square motifs with 90°/270° rotations
  • makeMotifSeamlessU32 reducing seamlessScore.horizontal on a hard red/blue seam
  • seamlessScore returning 0 on uniform motifs and wrap-safe cyclic gradients
  • Realistic-scale zero-allocation heuristic: 128² motif × 4×4 grid × 20 iters across every kernel, pre-allocated buffers
npm test          # ~200ms
npm run test:gc   # zero-gc suite with --expose-gc
npm run verify    # both, gate for publish

What this is not

  • Not a graphics library. Patternforge tiles motifs; it doesn't draw them. Bring your own procedural motif, upload, or SVG rasterization.
  • Not a resampler. Rotations are 90° increments only. Arbitrary rotation angles need pixel resampling (bilinear, bicubic) which is out of scope for a zero-GC kernel — do it upstream if you need it.
  • Not a color engine. Patternforge doesn't know about OKLCH, sRGB, gamut, or palettes. It moves pixels. Pair with @zakkster/lite-hueforge and @zakkster/lite-color-engine for the color half of surface design.
  • Not a WebGL renderer. Everything runs on the CPU against Uint32Array buffers. If you need >30 Mpx/second sustained, you want the GPU. For the tile counts real designers use (typically ≤ 4Mpx output), CPU is more than enough and simpler to compose.
  • Not a GUI. No React components, no drag-and-drop, no export dialogs. Bring your own UI; wire it to this kernel.

Ecosystem

Part of the @zakkster zero-GC stack:


License

MIT (c) Zahary Shinikchiev <[email protected]>