Move docs/ package mirror into document-navigator plugin
Relocate the ~6k-file docs tree from the repo root into plugins/document-navigator/docs/ so the plugin is self-contained and portable. docs/superpowers/ (specs and plans) stays at the repo root. Moved via cp+rm rather than git mv because directory rename() fails with EPERM on this WSL /mnt DrvFs mount; git rename-detection pairs the identical-content files at commit time. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
# @suaveplan/image-quantisation - Image complexity analysis and adaptive colour quantisation with pool detection, spatial-aware anti-aliasing cleanup, and multiple quantisation algorithms
|
||||
|
||||
Image complexity analysis and adaptive colour quantisation with pool detection, spatial-aware anti-aliasing cleanup, and multiple quantisation algorithms. Designed for emblem generation pipelines where pixel-art inputs need minimal processing while photographic inputs need aggressive reduction.
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
## Documentation tree
|
||||
|
||||
- [classify](./src/analysis/classify/classify.md) — Analyse a raw RGBA pixel buffer and classify its complexity into one of four tiers: pixel-art, vector-clean, vector-aa,…
|
||||
- [classify](./src/analysis/classify/classify.md) — Analyse a raw RGBA pixel buffer and classify its complexity into one of four tiers: pixel-art, vector-clean, vector-aa,…
|
||||
- [colour](./src/analysis/colour/colour.md) — Low-level colour distance and keying helpers used by the analysis and spatial-cleanup modules. These functions are pure…
|
||||
- [colour](./src/analysis/colour/colour.md) — Low-level colour distance and keying helpers used by the analysis and spatial-cleanup modules. These functions are pure…
|
||||
- [edges](./src/analysis/edges/edges.md) — Analyse colour transitions between horizontally and vertically adjacent pixels to determine whether an image has sharp…
|
||||
- [edges](./src/analysis/edges/edges.md) — Analyse colour transitions between horizontally and vertically adjacent pixels to determine whether an image has sharp…
|
||||
- [pools](./src/analysis/pools/pools.md) — Find contiguous regions of identical colour via 4-connected flood fill. Pool size distribution is the primary signal fo…
|
||||
- [pools](./src/analysis/pools/pools.md) — Find contiguous regions of identical colour via 4-connected flood fill. Pool size distribution is the primary signal fo…
|
||||
- [errors](./src/errors/image-quantisation-error.md) — Structured error type for all `@suaveplan/image-quantisation` failures. Extends `SuaveplanError` so that callers receiv…
|
||||
- [image-quantisation-error](./src/errors/image-quantisation-error.md) — Structured error type for all `@suaveplan/image-quantisation` failures. Extends `SuaveplanError` so that callers receiv…
|
||||
- [pipeline](./src/pipeline/pipeline.md) — Full workflow: analyse complexity → spatial cleanup → quantise. Automatically selects the best strategy based on image…
|
||||
- [pipeline](./src/pipeline/pipeline.md) — Full workflow: analyse complexity → spatial cleanup → quantise. Automatically selects the best strategy based on image…
|
||||
- [quantise](./src/quantise/quantise.md) — Reduce an RGBA pixel buffer to a target number of colours using the `image-q` library. This module is the primary quant…
|
||||
- [quantise](./src/quantise/quantise.md) — Reduce an RGBA pixel buffer to a target number of colours using the `image-q` library. This module is the primary quant…
|
||||
- [schemas](./src/schemas/options.schema.md) — Zod schemas for runtime validation of quantisation, spatial cleanup, and pipeline options. Every public entry point tha…
|
||||
- [options.schema](./src/schemas/options.schema.md) — Zod schemas for runtime validation of quantisation, spatial cleanup, and pipeline options. Every public entry point tha…
|
||||
- [cleanup](./src/spatial/cleanup/cleanup.md) — Merge small anti-aliasing pools into their nearest spatial neighbours by colour distance. This preserves intentional de…
|
||||
- [cleanup](./src/spatial/cleanup/cleanup.md) — Merge small anti-aliasing pools into their nearest spatial neighbours by colour distance. This preserves intentional de…
|
||||
- [types](./src/types/common.md) — The `common.ts` module defines the core domain types used across the `@suaveplan/image-quantisation` package. Every pub…
|
||||
- [common](./src/types/common.md) — The `common.ts` module defines the core domain types used across the `@suaveplan/image-quantisation` package. Every pub…
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
bun add @suaveplan/image-quantisation
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
```ts
|
||||
import {
|
||||
adaptivePipeline,
|
||||
analyseComplexity,
|
||||
quantise,
|
||||
} from "@suaveplan/image-quantisation";
|
||||
|
||||
// Full adaptive pipeline: analyse -> cleanup -> quantise
|
||||
const result = adaptivePipeline(rgbaPixels, width, height);
|
||||
|
||||
console.log(result.analysis.tier); // 'pixel-art' | 'vector-aa' | 'photographic'
|
||||
console.log(result.analysis.uniqueColours);
|
||||
console.log(result.finalColours); // colour count after quantisation
|
||||
// result.pixels is ready for downstream consumers
|
||||
```
|
||||
|
||||
## Modules
|
||||
|
||||
- [`src/analysis/classify/classify.md`](src/analysis/classify/classify.md) — Complexity classification
|
||||
- [`src/analysis/colour/colour.md`](src/analysis/colour/colour.md) — Colour distance and keying utilities
|
||||
- [`src/analysis/edges/edges.md`](src/analysis/edges/edges.md) — Edge sharpness analysis
|
||||
- [`src/analysis/pools/pools.md`](src/analysis/pools/pools.md) — Pool detection and distribution
|
||||
- [`src/errors/image-quantisation-error.md`](src/errors/image-quantisation-error.md) — Structured error types
|
||||
- [`src/pipeline/pipeline.md`](src/pipeline/pipeline.md) — Adaptive pipeline
|
||||
- [`src/quantise/quantise.md`](src/quantise/quantise.md) — Colour quantisation
|
||||
- [`src/schemas/options.schema.md`](src/schemas/options.schema.md) — Zod validation schemas
|
||||
- [`src/spatial/cleanup/cleanup.md`](src/spatial/cleanup/cleanup.md) — Spatial AA cleanup
|
||||
- [`src/types/common.md`](src/types/common.md) — Core type definitions
|
||||
|
||||
## Features
|
||||
|
||||
- Image complexity classification into four tiers: pixel-art, vector-clean, vector-aa, photographic
|
||||
- Pool detection via 4-connected flood fill for contiguous same-colour regions
|
||||
- Pool distribution statistics (count, min, max, mean, median, singleton ratio)
|
||||
- Edge analysis with sharpness ratio and mean colour distance
|
||||
- Spatial AA cleanup that merges small pools into nearest spatial neighbours
|
||||
- Three quantisation algorithms: wuquant (Wu), neuquant (neural network), rgbquant (fast)
|
||||
- Three colour distance formulas: euclidean, manhattan, ciede2000
|
||||
- Dithering options: none, floyd-steinberg, riemersma
|
||||
- Full adaptive pipeline: analyse, cleanup, quantise in one call
|
||||
- Colour utility functions for distance calculation and key generation
|
||||
- Zod schemas for runtime option validation
|
||||
- OpenTelemetry counter and histogram instrumentation
|
||||
- Structured errors with stable codes via `@suaveplan/error`
|
||||
- Property-based tests with fast-check
|
||||
- 100% test coverage (Istanbul)
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
# Complexity Classification
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
Analyse a raw RGBA pixel buffer and classify its complexity into one of four tiers: pixel-art, vector-clean, vector-aa, or photographic. The tier determines the optimal processing strategy for emblem generation pipelines. This module combines pool detection, edge analysis, and heuristics into a single high-level entry point.
|
||||
|
||||
## Features
|
||||
|
||||
- Four-tier classification based on pool distribution and edge sharpness
|
||||
- Automatic palette size recommendation
|
||||
- Dithering recommendation for photographic content
|
||||
- Pure TypeScript with zero external dependencies for analysis
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { analyseComplexity } from "@suaveplan/image-quantisation/analysis/classify";
|
||||
|
||||
const analysis = analyseComplexity(pixels, 32, 32);
|
||||
console.log(analysis.tier); // "pixel-art" | "photographic" | ...
|
||||
console.log(analysis.recommendedColours); // suggested palette size
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { analyseComplexity } from "@suaveplan/image-quantisation/analysis/classify";
|
||||
|
||||
const analysis = analyseComplexity(pixels, 128, 128);
|
||||
|
||||
const strategy =
|
||||
analysis.tier === "pixel-art"
|
||||
? "skip_quantise"
|
||||
: analysis.tier === "photographic"
|
||||
? "heavy_dither"
|
||||
: "light_merge";
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `analyseComplexity(pixels, width, height): ComplexityAnalysis`
|
||||
|
||||
Runs pool detection, edge analysis, and tier classification in one call.
|
||||
|
||||
Returns a `ComplexityAnalysis` object containing:
|
||||
|
||||
- `tier` — classification result
|
||||
- `uniqueColours` — number of distinct colours
|
||||
- `poolDistribution` — pool size statistics
|
||||
- `edgeAnalysis` — edge sharpness metrics
|
||||
- `recommendedColours` — suggested palette size (2–256)
|
||||
- `recommendDithering` — boolean
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
Classification heuristics are tuned for emblem-generation workflows:
|
||||
|
||||
- Pixel art: ≤32 colours, mean pool size >20
|
||||
- Photographic: >70% singleton pools, sharpness <0.05
|
||||
- Vector-AA: bimodal distribution with many tiny edge pools
|
||||
|
||||
Recommended colours are clamped to a minimum of 2 to satisfy quantiser constraints. The heuristics were derived from empirical testing on a corpus of pixel-art, vector, and photographic images. They are intentionally simple to keep the analysis fast and deterministic. Future versions may expose the threshold constants as configurable parameters.
|
||||
@@ -0,0 +1,70 @@
|
||||
# Colour Utilities
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
Low-level colour distance and keying helpers used by the analysis and spatial-cleanup modules. These functions are pure, stateless, and operate on the `RGBA` type. They enable exact colour matching for pool detection, perceptual distance for edge analysis, and canonical string keys for hash-based lookups. All operations are synchronous and allocate no intermediate objects beyond the return value.
|
||||
|
||||
## Features
|
||||
|
||||
- Euclidean RGB distance
|
||||
- Perceptually weighted Euclidean distance (ITU-R BT.601)
|
||||
- Canonical colour key generation for exact matching
|
||||
- Round-trip colour key parsing
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import {
|
||||
rgbDistance,
|
||||
colourKey,
|
||||
} from "@suaveplan/image-quantisation/analysis/colour";
|
||||
|
||||
const a = { r: 255, g: 0, b: 0, a: 255 };
|
||||
const b = { r: 0, g: 255, b: 0, a: 255 };
|
||||
|
||||
console.log(rgbDistance(a, b)); // ~360.6
|
||||
console.log(colourKey(a)); // "255,0,0,255"
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import {
|
||||
weightedDistance,
|
||||
parseColourKey,
|
||||
} from "@suaveplan/image-quantisation/analysis/colour";
|
||||
|
||||
// Weighted distance accounts for human eye sensitivity
|
||||
const dist = weightedDistance(
|
||||
{ r: 100, g: 100, b: 100, a: 255 },
|
||||
{ r: 100, g: 110, b: 100, a: 255 }
|
||||
);
|
||||
|
||||
const parsed = parseColourKey("100,110,100,255");
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `rgbDistance(a, b): number`
|
||||
|
||||
Euclidean distance in RGB space. Range 0–441.67.
|
||||
|
||||
### `weightedDistance(a, b): number`
|
||||
|
||||
ITU-R BT.601 weighted Euclidean distance. Range 0–255.
|
||||
|
||||
### `colourKey(c): string`
|
||||
|
||||
Returns `"r,g,b,a"`.
|
||||
|
||||
### `parseColourKey(key): RGBA`
|
||||
|
||||
Parses a comma-separated key back into `RGBA`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
`rgbDistance` ignores the alpha channel. `weightedDistance` uses coefficients 0.299 (R), 0.587 (G), 0.114 (B) to approximate perceptual difference. The alpha channel is included in `colourKey` so that transparent pools are not merged with opaque ones during flood fill. All four functions are pure and have no side effects, making them safe to use in property-based tests and benchmarks. They are also fully deterministic: given the same inputs they always produce the same outputs, which is important for reproducible image processing pipelines.
|
||||
@@ -0,0 +1,53 @@
|
||||
# Edge Analysis
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
Analyse colour transitions between horizontally and vertically adjacent pixels to determine whether an image has sharp edges (pixel art) or soft edges (photographic). Edge metrics are a critical input to the complexity classifier, providing a signal that is orthogonal to pool distribution. Two images may have the same number of pools but very different edge characteristics.
|
||||
|
||||
## Features
|
||||
|
||||
- Examines every right and bottom neighbour pair
|
||||
- Counts hard edges (perceptually distinct) and soft edges (nearly identical)
|
||||
- Computes sharpness ratio and mean edge distance
|
||||
- Configurable hard/soft thresholds
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { analyseEdges } from "@suaveplan/image-quantisation/analysis/edges";
|
||||
|
||||
const edges = analyseEdges(pixels, 32, 32);
|
||||
console.log(edges.sharpnessRatio); // 0.0 – 1.0
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { analyseEdges } from "@suaveplan/image-quantisation/analysis/edges";
|
||||
|
||||
// Stricter thresholds for high-contrast artwork
|
||||
const edges = analyseEdges(pixels, 64, 64, 20, 5);
|
||||
|
||||
if (edges.sharpnessRatio > 0.5) {
|
||||
console.log("High-contrast pixel art detected");
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `analyseEdges(pixels, width, height, hardThreshold?, softThreshold?): EdgeAnalysis`
|
||||
|
||||
- `pixels` — RGBA `Uint8Array`
|
||||
- `width`, `height` — image dimensions
|
||||
- `hardThreshold` — default 30
|
||||
- `softThreshold` — default 10
|
||||
|
||||
Returns `EdgeAnalysis` with `totalEdges`, `hardEdges`, `softEdges`, `sharpnessRatio`, `meanEdgeDistance`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The function uses `weightedDistance` (ITU-R BT.601) for perceptually fair comparisons. Pairs are counted once each (right and bottom neighbours only) to avoid double counting. A solid-colour image produces zero hard edges and zero mean distance, while a checkerboard produces a very high sharpness ratio. The thresholds can be adjusted for different content types: stricter thresholds treat more edges as hard, which is useful for high-contrast artwork, while lenient thresholds classify most edges as soft, which is better for smooth gradients.
|
||||
@@ -0,0 +1,56 @@
|
||||
# Pool Detection
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
Find contiguous regions of identical colour via 4-connected flood fill. Pool size distribution is the primary signal for image complexity classification. A pixel-art image may have only a handful of large pools, while a photograph may have thousands of singleton pools. This module provides both detection and statistical summary functions.
|
||||
|
||||
## Features
|
||||
|
||||
- 4-connected BFS flood fill
|
||||
- Returns pools sorted by size descending
|
||||
- Computes distribution statistics
|
||||
- Zero dependencies beyond colour utilities
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { detectPools } from "@suaveplan/image-quantisation/analysis/pools";
|
||||
|
||||
const pools = detectPools(pixels, 32, 32);
|
||||
console.log(pools.length); // number of distinct colour regions
|
||||
console.log(pools[0]!.size); // largest region
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import {
|
||||
detectPools,
|
||||
poolDistribution,
|
||||
} from "@suaveplan/image-quantisation/analysis/pools";
|
||||
|
||||
const pools = detectPools(pixels, 64, 64);
|
||||
const dist = poolDistribution(pools);
|
||||
|
||||
if (dist.singletonRatio > 0.7) {
|
||||
console.log("Photographic — many 1-pixel pools");
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `detectPools(pixels, width, height): Pool[]`
|
||||
|
||||
Returns an array of `Pool` objects sorted by size descending.
|
||||
|
||||
### `poolDistribution(pools): PoolDistribution`
|
||||
|
||||
Computes count, min, max, mean, median, p90, singletons, and singletonRatio.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
Flood fill uses a stack-based BFS with a `Uint8Array` visited mask for memory efficiency. The `pixels` buffer is read-only. Pools are sorted by size descending so that the dominant colour regions appear first, which is useful for downstream algorithms that only need to inspect the largest pools. The visited mask uses a single byte per pixel, which keeps memory overhead low even for large images. Because every pixel is visited exactly once, the overall time complexity is O(width * height) in the worst case. For images with many small pools, the BFS queue may grow large, but the `Uint8Array` visited mask ensures that each pixel is never enqueued more than once.
|
||||
@@ -0,0 +1,70 @@
|
||||
# QuantisationError
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
Structured error type for all `@suaveplan/image-quantisation` failures. Extends `SuaveplanError` so that callers receive stable codes, i18n-ready message keys, and optional cause chains. This unified error type eliminates the need for consumers to catch multiple different exception classes and enables programmatic branching on the `code` property.
|
||||
|
||||
## Features
|
||||
|
||||
- Stable `QUANTISATION.*` error codes via `defineCodes`
|
||||
- Typed `code` property for programmatic discrimination
|
||||
- i18n-ready message keys with no hard-coded user strings
|
||||
- OTel span-event compatibility via `toSpanEvent`
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import {
|
||||
QuantisationError,
|
||||
QUANTISATION_CODES,
|
||||
} from "@suaveplan/image-quantisation/errors";
|
||||
|
||||
throw new QuantisationError(
|
||||
QUANTISATION_CODES.INVALID_DIMENSIONS,
|
||||
"quantisation.invalid_dimensions"
|
||||
);
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import {
|
||||
QuantisationError,
|
||||
QUANTISATION_CODES,
|
||||
} from "@suaveplan/image-quantisation/errors";
|
||||
|
||||
try {
|
||||
quantise(pixels, 0, 1);
|
||||
} catch (err) {
|
||||
if (err instanceof QuantisationError) {
|
||||
console.log(err.code); // "QUANTISATION.INVALID_DIMENSIONS"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `QUANTISATION_CODES`
|
||||
|
||||
Constant object exported from `defineCodes("QUANTISATION", [...])`.
|
||||
|
||||
- `INVALID_PIXEL_BUFFER`
|
||||
- `INVALID_COLOR_COUNT`
|
||||
- `INVALID_DIMENSIONS`
|
||||
- `PROCESSING_FAILED`
|
||||
|
||||
### `QuantisationError`
|
||||
|
||||
Constructor: `new QuantisationError(code, messageKey, options?)`
|
||||
|
||||
- `code` — one of the `QUANTISATION_CODES` values
|
||||
- `messageKey` — i18n message resolver key
|
||||
- `options?.cause` — optional underlying `Error`
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
Error codes are registered at module load time. The `defineCodes` utility guarantees that every code string is prefixed with the namespace, so collisions across packages are impossible. The `INVALID_COLOR_COUNT` code is reserved for future use when palette-size validation is tightened. Consumers should always test against the exported `QUANTISATION_CODES` constant rather than hard-coding strings, because the exact prefix may change during major version bumps. The `QuantisationError` class is kept in its own feature module so that consumers who only need the error type can import it without pulling in the full quantisation logic.
|
||||
@@ -0,0 +1,67 @@
|
||||
# Adaptive Pipeline
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
Full workflow: analyse complexity → spatial cleanup → quantise. Automatically selects the best strategy based on image content. This is the recommended high-level entry point for emblem-generation pipelines because it encapsulates the decision logic for when to skip quantisation, when to apply spatial cleanup, and when to use dithering.
|
||||
|
||||
## Features
|
||||
|
||||
- One-call image preparation for emblem-solver
|
||||
- Skips quantisation for pixel-art images
|
||||
- Applies spatial cleanup for vector-aa and vector-clean tiers
|
||||
- Auto-selects dithering for photographic content
|
||||
- Zod validation for all options
|
||||
- OTel counter and histogram instrumentation
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { adaptivePipeline } from "@suaveplan/image-quantisation/pipeline";
|
||||
|
||||
const result = adaptivePipeline(pixels, 32, 32);
|
||||
console.log(result.analysis.tier);
|
||||
console.log(result.finalColours);
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { adaptivePipeline } from "@suaveplan/image-quantisation/pipeline";
|
||||
|
||||
const result = adaptivePipeline(pixels, 64, 64, {
|
||||
maxColours: 16,
|
||||
dither: "floyd-steinberg",
|
||||
spatial: { maxDistance: 25, maxPoolSize: 2 },
|
||||
skipQuantisationForPixelArt: false,
|
||||
});
|
||||
|
||||
if (result.spatialCleanupApplied) {
|
||||
console.log("Spatial cleanup merged small AA pools");
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `adaptivePipeline(pixels, width, height, options?): AdaptivePipelineResult`
|
||||
|
||||
- `pixels` — RGBA `Uint8Array`
|
||||
- `width`, `height` — image dimensions
|
||||
- `options` — `AdaptivePipelineOptions` (optional)
|
||||
|
||||
Returns `AdaptivePipelineResult` with:
|
||||
|
||||
- `pixels` — final processed buffer
|
||||
- `analysis` — complexity analysis of the original
|
||||
- `quantisation` — quantisation result or `null`
|
||||
- `spatialCleanupApplied` — boolean
|
||||
- `finalColours` — unique colour count after processing
|
||||
|
||||
Throws `QuantisationError` on invalid input or options.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The pipeline never mutates the input buffer. Each stage creates a new `Uint8Array` when modification is required. OTel instruments record the complexity tier and whether spatial cleanup was applied as attributes. The final colour count is computed by packing each RGBA tuple into a 32-bit integer and counting distinct values in a `Set`. This design makes the pipeline safe to call repeatedly on the same buffer without copying at the call site. The `skipQuantisationForPixelArt` default is true because pixel-art images already have a low colour count and quantisation would only degrade quality.
|
||||
@@ -0,0 +1,66 @@
|
||||
# Quantise
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
Reduce an RGBA pixel buffer to a target number of colours using the `image-q` library. This module is the primary quantisation entry point, supporting three palette-generation algorithms and three dithering modes. It wraps the low-level `image-q` API with ergonomic options, Zod validation, and OpenTelemetry instrumentation.
|
||||
|
||||
## Features
|
||||
|
||||
- Wu quant (median-cut, best quality)
|
||||
- Neuquant (neural network, good for photos)
|
||||
- RGBQuant (fastest, good for previews)
|
||||
- Euclidean, Manhattan, and CIEDE2000 distance formulas
|
||||
- Floyd-Steinberg and Riemersma dithering
|
||||
- Zod option validation
|
||||
- OTel counter and histogram instrumentation
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { quantise } from "@suaveplan/image-quantisation/quantise";
|
||||
|
||||
const result = quantise(pixels, 32, 32, {
|
||||
colours: 16,
|
||||
algorithm: "wuquant",
|
||||
});
|
||||
|
||||
console.log(result.paletteSize); // ≤ 16
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { quantise } from "@suaveplan/image-quantisation/quantise";
|
||||
|
||||
const result = quantise(pixels, 64, 64, {
|
||||
colours: 32,
|
||||
algorithm: "neuquant",
|
||||
distance: "ciede2000",
|
||||
dither: "floyd-steinberg",
|
||||
});
|
||||
|
||||
// result.pixels is a new Uint8Array with quantised colours
|
||||
// result.palette is the generated colour table
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `quantise(pixels, width, height, options?): QuantiseResult`
|
||||
|
||||
- `pixels` — RGBA `Uint8Array`
|
||||
- `width`, `height` — image dimensions
|
||||
- `options` — `QuantiseOptions` (optional)
|
||||
|
||||
Throws `QuantisationError` on invalid input dimensions, mismatched buffer size, or invalid options.
|
||||
|
||||
### `quantiseAdaptive(pixels, width, height, recommendedColours, recommendDithering, overrides?): QuantiseResult`
|
||||
|
||||
Convenience wrapper that sets algorithm to `"wuquant"` and dither based on the boolean flag. `overrides` can override any individual field.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The function validates inputs with Zod schemas before delegating to `image-q`. OTel instruments are lazily initialised on first use to avoid side effects at module load time. All algorithms run synchronously. The `mapDither` helper translates our dither enum values into the strings expected by `image-q`. Performance varies by algorithm: Wu quant is generally the best balance of quality and speed, Neuquant excels on photographic content, and RGBQuant is fastest but may produce slightly lower quality. Callers should benchmark on their specific image corpus to choose the best algorithm.
|
||||
@@ -0,0 +1,70 @@
|
||||
# Option Schemas
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
Zod schemas for runtime validation of quantisation, spatial cleanup, and pipeline options. Every public entry point that accepts options validates them through these schemas before processing. This catches misconfigured callers early and produces descriptive error messages that can be forwarded to end users or logged without additional formatting.
|
||||
|
||||
## Features
|
||||
|
||||
- `QuantiseOptionsSchema` — validates colour count, algorithm, distance formula, and dithering
|
||||
- `SpatialCleanupOptionsSchema` — validates merge thresholds
|
||||
- `AdaptivePipelineOptionsSchema` — validates pipeline overrides
|
||||
- Descriptive error messages suitable for structured logging
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { QuantiseOptionsSchema } from "@suaveplan/image-quantisation/schemas";
|
||||
|
||||
const result = QuantiseOptionsSchema.safeParse({ colours: 16 });
|
||||
if (!result.success) {
|
||||
console.log(result.error.issues);
|
||||
}
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import {
|
||||
AdaptivePipelineOptionsSchema,
|
||||
SpatialCleanupOptionsSchema,
|
||||
} from "@suaveplan/image-quantisation/schemas";
|
||||
|
||||
const raw = {
|
||||
maxColours: 32,
|
||||
spatial: { maxDistance: 20 },
|
||||
};
|
||||
|
||||
const parsed = AdaptivePipelineOptionsSchema.parse(raw);
|
||||
// parsed.spatial is fully typed and defaults are applied by Zod
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `QuantiseOptionsSchema`
|
||||
|
||||
- `colours` — optional integer 2–256
|
||||
- `algorithm` — optional enum `"neuquant" | "rgbquant" | "wuquant"`
|
||||
- `distance` — optional enum `"euclidean" | "manhattan" | "ciede2000"`
|
||||
- `dither` — optional enum `"none" | "floyd-steinberg" | "riemersma"`
|
||||
|
||||
### `SpatialCleanupOptionsSchema`
|
||||
|
||||
- `maxDistance` — optional non-negative number
|
||||
- `maxPoolSize` — optional non-negative integer
|
||||
|
||||
### `AdaptivePipelineOptionsSchema`
|
||||
|
||||
- `maxColours` — optional integer 2–256
|
||||
- `dither` — optional dither enum
|
||||
- `spatial` — optional nested `SpatialCleanupOptionsSchema`
|
||||
- `skipSpatialCleanup` — optional boolean
|
||||
- `skipQuantisationForPixelArt` — optional boolean
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
Schemas use `.optional()` for every field so that callers can supply partial options and the implementation supplies defaults. Validation failures throw `QuantisationError` with code `PROCESSING_FAILED`. The schemas are co-located with the types they validate to reduce drift. The `QuantiseOptionsSchema` enforces a minimum of 2 colours because single-colour palettes are not supported by the underlying `image-q` library. The `AdaptivePipelineOptionsSchema` nests `SpatialCleanupOptionsSchema` so that pipeline callers can override cleanup parameters in a single options object.
|
||||
@@ -0,0 +1,69 @@
|
||||
# Spatial Cleanup
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
Merge small anti-aliasing pools into their nearest spatial neighbours by colour distance. This preserves intentional detail while removing AA noise. Unlike global k-means quantisation, spatial cleanup only merges pools that are physically adjacent, so a small blue dot next to a red region stays blue while a near-red edge pixel gets absorbed into the red region.
|
||||
|
||||
## Features
|
||||
|
||||
- Spatially-aware merge (not global k-means)
|
||||
- Configurable colour distance and pool size thresholds
|
||||
- Returns a new pixel buffer (non-destructive)
|
||||
- Fast pixel-to-pool index lookup via `Int32Array`
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import {
|
||||
detectPools,
|
||||
spatialCleanup,
|
||||
} from "@suaveplan/image-quantisation";
|
||||
|
||||
const pools = detectPools(pixels, 32, 32);
|
||||
const cleaned = spatialCleanup(pixels, 32, 32, pools);
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import {
|
||||
detectPools,
|
||||
spatialCleanup,
|
||||
countUniqueColours,
|
||||
} from "@suaveplan/image-quantisation";
|
||||
|
||||
const pools = detectPools(pixels, 64, 64);
|
||||
const before = countUniqueColours(pixels, 64, 64);
|
||||
|
||||
const cleaned = spatialCleanup(pixels, 64, 64, pools, {
|
||||
maxDistance: 25,
|
||||
maxPoolSize: 3,
|
||||
});
|
||||
|
||||
const after = countUniqueColours(cleaned, 64, 64);
|
||||
console.log(`Removed ${before - after} colours`);
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `spatialCleanup(pixels, width, height, pools, options?): Uint8Array`
|
||||
|
||||
- `pixels` — RGBA `Uint8Array`
|
||||
- `width`, `height` — image dimensions
|
||||
- `pools` — array of `Pool` from `detectPools`
|
||||
- `options.maxDistance` — default 30
|
||||
- `options.maxPoolSize` — default 4
|
||||
|
||||
Returns a new `Uint8Array` with merged pixels.
|
||||
|
||||
### `countUniqueColours(pixels, width, height): number`
|
||||
|
||||
Counts distinct colours in the buffer using a 32-bit packed key.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The algorithm builds an adjacency map for pools that are physically adjacent in the image. For each small pool, it finds the neighbour with the minimum `rgbDistance`. If the distance is within `maxDistance`, the pool is recoloured. The pixel-to-pool index is stored in an `Int32Array` initialised to `-1` for fast O(1) lookup during adjacency building.
|
||||
@@ -0,0 +1,114 @@
|
||||
# Common Types
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
The `common.ts` module defines the core domain types used across the `@suaveplan/image-quantisation` package. Every public function operates on these shapes, so consumers should import them when writing TypeScript wrappers or adapters. Keeping types in a single module ensures consistency between analysis, quantisation, and pipeline stages. The types are designed to be serialisable, tree-shakeable, and free of runtime dependencies.
|
||||
|
||||
## Features
|
||||
|
||||
- Strongly-typed RGBA pixel representation
|
||||
- Pool and edge-analysis result shapes
|
||||
- Quantisation option and result types
|
||||
- Adaptive pipeline input/output types
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import type { RGBA, QuantiseOptions } from "@suaveplan/image-quantisation/types";
|
||||
|
||||
const pixel: RGBA = { r: 255, g: 128, b: 64, a: 255 };
|
||||
|
||||
const opts: QuantiseOptions = {
|
||||
colours: 16,
|
||||
algorithm: "wuquant",
|
||||
distance: "euclidean",
|
||||
dither: "none",
|
||||
};
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import type {
|
||||
AdaptivePipelineOptions,
|
||||
ComplexityAnalysis,
|
||||
} from "@suaveplan/image-quantisation/types";
|
||||
|
||||
function buildPipelineOpts(
|
||||
analysis: ComplexityAnalysis
|
||||
): AdaptivePipelineOptions {
|
||||
return {
|
||||
maxColours: analysis.recommendedColours,
|
||||
dither: analysis.recommendDithering ? "floyd-steinberg" : "none",
|
||||
spatial: { maxDistance: 25, maxPoolSize: 3 },
|
||||
skipSpatialCleanup: false,
|
||||
skipQuantisationForPixelArt: true,
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `RGBA`
|
||||
|
||||
Interface with `r`, `g`, `b`, `a` in the range 0–255.
|
||||
|
||||
### `Pool`
|
||||
|
||||
Represents a contiguous region of identical colour found via 4-connected flood fill.
|
||||
|
||||
### `PoolDistribution`
|
||||
|
||||
Statistics object: `count`, `min`, `max`, `mean`, `median`, `p90`, `singletons`, `singletonRatio`.
|
||||
|
||||
### `EdgeAnalysis`
|
||||
|
||||
Metrics: `totalEdges`, `hardEdges`, `softEdges`, `sharpnessRatio`, `meanEdgeDistance`.
|
||||
|
||||
### `ComplexityTier`
|
||||
|
||||
Union type: `"pixel-art" | "vector-clean" | "vector-aa" | "photographic"`.
|
||||
|
||||
### `ComplexityAnalysis`
|
||||
|
||||
Full analysis including tier, pools, edges, and recommendations.
|
||||
|
||||
### `PaletteAlgorithm`
|
||||
|
||||
`"neuquant" | "rgbquant" | "wuquant"`.
|
||||
|
||||
### `DistanceFormula`
|
||||
|
||||
`"euclidean" | "manhattan" | "ciede2000"`.
|
||||
|
||||
### `DitherAlgorithm`
|
||||
|
||||
`"none" | "floyd-steinberg" | "riemersma"`.
|
||||
|
||||
### `QuantiseOptions`
|
||||
|
||||
Optional fields: `colours`, `algorithm`, `distance`, `dither`.
|
||||
|
||||
### `QuantiseResult`
|
||||
|
||||
Result with `pixels`, `palette`, `paletteSize`, and the resolved `options`.
|
||||
|
||||
### `SpatialCleanupOptions`
|
||||
|
||||
Optional fields: `maxDistance`, `maxPoolSize`.
|
||||
|
||||
### `AdaptivePipelineOptions`
|
||||
|
||||
Optional fields: `maxColours`, `dither`, `spatial`, `skipSpatialCleanup`, `skipQuantisationForPixelArt`.
|
||||
|
||||
### `AdaptivePipelineResult`
|
||||
|
||||
Full pipeline result with analysis, optional quantisation, and final pixel buffer.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
All interfaces are pure data shapes with no methods. They are designed to be serialisable and tree-shakeable. The `Pool` interface uses `Set<number>` for pixel indices, which is efficient for BFS traversal but may be replaced with a denser representation in future versions if memory becomes a concern for very large images.
|
||||
Reference in New Issue
Block a user