first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,75 @@
# `grid/analyse/analyse.ts` — colour groups, islands, and shape stats
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
This module owns `analyseGrid`, the diagnostic phase of the conversion
pipeline. It walks an RGBA grid once, buckets opaque pixels by colour, runs
a 4-connected flood-fill on each bucket to recover the islands, and
computes per-colour `density` (`pixels / bbox area`) and `fragmentation`
(`islands / pixels`). The result feeds the layer-ordering heuristics and
the per-island cover strategy in the solver pipeline.
## Features
- Single linear pass over the grid to populate `opaquePixels` and the
colour buckets.
- 4-connected BFS flood-fill — diagonals are treated as separate islands,
matching the upstream reference and the convention used by every
pixel-art tool.
- Density and fragmentation are pure functions of the bucket and its
bounding box; the solver uses both to score the `island-aware` ordering.
- Output is structurally shared with the rest of the package via
`GridAnalysis` — solver kernels consume `colourGroups`/`colourAnalysis`
directly.
## Usage: Basic
```ts
import { analyseGrid } from "@suaveplan/emblem-solver";
const { colourGroups, colourAnalysis } = analyseGrid(pixels, w, h);
console.log("colours:", colourGroups.size);
```
## Usage: Advanced
```ts
import { analyseGrid } from "@suaveplan/emblem-solver";
function pickBackgroundColour(pixels, w, h) {
const a = analyseGrid(pixels, w, h);
let best = "";
let bestScore = -Infinity;
for (const [key, info] of a.colourAnalysis) {
const score = info.totalPixels * info.density;
if (score > bestScore) {
best = key;
bestScore = score;
}
}
return best;
}
```
## API Reference
| Symbol | Kind | Description |
|---|---|---|
| `analyseGrid(pixels, w, h)` | fn | Returns `GridAnalysis` |
## Implementation Notes
The flood-fill uses an explicit stack (`pop()`) rather than recursion to
avoid blowing the call stack on large emblems. We `add` to `visited`
*before* enqueueing the neighbour, which guarantees each pixel is touched
at most twice (once when discovered, once when its packed index is popped).
The neighbour set is computed as a small inline array of `-1`-sentinel
checks for the four cardinal directions; benchmarks against a `for (const
[dx,dy] …)` form showed the inline form measurably faster in V8 and Bun.
The `Map<string, Set<number>>` colour-group layout is intentional: the
string key is the canonical colour identity, while the packed-index `Set`
gives O(1) `has`/`add`/`delete` for the cover strategies.