daemon-sec-lotl

DÆMONBins: GTFOBins × LOLBAS × WADComs × LOOBins in one filterable catalog
git clone https://git.daemon-sec.xyz/daemon-sec-lotl.git
Log | Files | Refs | Submodules | README | LICENSE

icon-targets.ts (3307B)


      1 /**
      2  * From an icon's outline to a landing spot for every particle.
      3  *
      4  * The icon arrives as SVG markup (a lucide component's `outerHTML`),
      5  * is drawn once into a small offscreen canvas, and its alpha becomes a
      6  * grid of "ink here" cells. Each particle then needs the nearest inked
      7  * cell to its own resting place: a multi-source flood fill from every
      8  * inked cell gives that for the whole grid at once, in a millisecond,
      9  * which is near enough to true nearest at this resolution. The spot is
     10  * jittered inside its cell so a stroke does not read as a row of beads.
     11  *
     12  * Coordinates are icon-local: `[-0.5, 0.5]` on both axes, y up. The
     13  * field places and scales them per frame.
     14  */
     15 
     16 export const GRID = 128;
     17 const PAD = 8;
     18 const INK = 96;
     19 
     20 /** Draw the markup and read its alpha. Async: image decode. */
     21 export async function rasterise(svg: string, canvas: HTMLCanvasElement): Promise<Uint8Array> {
     22   const img = new Image();
     23   img.src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`;
     24   await img.decode();
     25   canvas.width = GRID;
     26   canvas.height = GRID;
     27   const ctx = canvas.getContext("2d", { willReadFrequently: true });
     28   if (!ctx) throw new Error("no 2d context");
     29   ctx.clearRect(0, 0, GRID, GRID);
     30   ctx.drawImage(img, PAD, PAD, GRID - PAD * 2, GRID - PAD * 2);
     31   const { data } = ctx.getImageData(0, 0, GRID, GRID);
     32   const out = new Uint8Array(GRID * GRID);
     33   for (let i = 0; i < out.length; i++) out[i] = data[i * 4 + 3] > INK ? 1 : 0;
     34   return out;
     35 }
     36 
     37 /** For every cell, the index of the nearest inked cell (8-connected BFS). */
     38 export function nearestInked(ink: Uint8Array): Int32Array {
     39   const n = GRID * GRID;
     40   const src = new Int32Array(n).fill(-1);
     41   const queue = new Int32Array(n);
     42   let head = 0;
     43   let tail = 0;
     44   for (let i = 0; i < n; i++) {
     45     if (ink[i]) {
     46       src[i] = i;
     47       queue[tail++] = i;
     48     }
     49   }
     50   while (head < tail) {
     51     const c = queue[head++];
     52     const cx = c % GRID;
     53     const cy = (c - cx) / GRID;
     54     for (let dy = -1; dy <= 1; dy++) {
     55       const y = cy + dy;
     56       if (y < 0 || y >= GRID) continue;
     57       for (let dx = -1; dx <= 1; dx++) {
     58         const x = cx + dx;
     59         if (x < 0 || x >= GRID) continue;
     60         const i = y * GRID + x;
     61         if (src[i] >= 0) continue;
     62         src[i] = src[c];
     63         queue[tail++] = i;
     64       }
     65     }
     66   }
     67   return src;
     68 }
     69 
     70 /**
     71  * Landing spots for `rest` (icon-local pairs), one per particle, from
     72  * the nearest-ink map. Returns icon-local pairs, y up.
     73  */
     74 export function targetsFor(rest: Float32Array, nearest: Int32Array, hash: (i: number) => number): Float32Array {
     75   const count = rest.length / 2;
     76   const out = new Float32Array(rest.length);
     77   for (let i = 0; i < count; i++) {
     78     const rx = rest[i * 2];
     79     const ry = rest[i * 2 + 1];
     80     const cx = Math.min(GRID - 1, Math.max(0, Math.floor((rx + 0.5) * GRID)));
     81     const cy = Math.min(GRID - 1, Math.max(0, Math.floor((0.5 - ry) * GRID)));
     82     const s = nearest[cy * GRID + cx];
     83     if (s < 0) {
     84       out[i * 2] = rx;
     85       out[i * 2 + 1] = ry;
     86       continue;
     87     }
     88     const sx = s % GRID;
     89     const sy = (s - sx) / GRID;
     90     const jx = hash(i * 2) - 0.5;
     91     const jy = hash(i * 2 + 1) - 0.5;
     92     out[i * 2] = (sx + 0.5 + jx * 0.9) / GRID - 0.5;
     93     out[i * 2 + 1] = 0.5 - (sy + 0.5 + jy * 0.9) / GRID;
     94   }
     95   return out;
     96 }