Initial Commit

This commit is contained in:
2026-05-27 23:29:53 +01:00
commit fbc70f96e6
10000 changed files with 1102152 additions and 0 deletions

View File

@@ -0,0 +1,228 @@
<!-- markdownlint-disable MD024 -->
# Capability Spec Delta: image-quantisation
**Package**: `@suaveplan/image-quantisation`
**Status**: PROPOSED
> Every requirement below carries a `**Verified by:**` line at authoring time. No exceptions.
## ADDED Requirements
### Requirement: Package provides image complexity analysis
The package SHALL analyse raw RGBA pixel buffers and classify image complexity into one of four tiers: pixel-art, vector-clean, vector-aa, or photographic.
#### Scenario: Solid-colour image classified as pixel-art
- **WHEN** a solid-colour 32×32 buffer is passed to `analyseComplexity`
- **THEN** the returned `tier` is `"pixel-art"`, `uniqueColours` is 1, and `recommendedColours` ≤ 32
**Verified by:** `src/analysis/classify/classify.test.ts::"classifies solid colour as pixel-art"`
#### Scenario: Gradient classified as photographic
- **WHEN** a smooth gradient 64×64 buffer is passed to `analyseComplexity`
- **THEN** the returned `tier` is `"photographic"`, `uniqueColours` > 100, and `recommendDithering` is `true`
**Verified by:** `src/analysis/classify/classify.test.ts::"classifies gradient as photographic"`
#### Scenario: Vector-clean tier detected
- **WHEN** a 32×32 image with 64 colours in 4×4 blocks is analysed
- **THEN** the returned `tier` is `"vector-clean"`
**Verified by:** `src/analysis/classify/classify.test.ts::"classifies vector-clean for moderate colours with medium pools"`
#### Scenario: Vector-aa tier detected
- **WHEN** a 64×64 image with 256 colours in 4×4 blocks is analysed
- **THEN** the returned `tier` is `"vector-aa"`
**Verified by:** `src/analysis/classify/classify.test.ts::"classifies vector-aa for high colour count with structure"`
---
### Requirement: Package provides pool detection and distribution
The package SHALL detect contiguous same-colour regions via 4-connected flood fill and compute pool size distribution statistics.
#### Scenario: Single pool for solid colour
- **WHEN** `detectPools` is called on an 8×8 solid red buffer
- **THEN** it returns exactly one pool with `size` 64 and colour `{r:255,g:0,b:0,a:255}`
**Verified by:** `src/analysis/pools/pools.test.ts::"finds 1 pool for a solid colour"`
#### Scenario: Pool distribution computed correctly
- **WHEN** `poolDistribution` is called on pools from a checkerboard
- **THEN** `singletonRatio` is 1 and `mean` is 1
**Verified by:** `src/analysis/pools/pools.test.ts::"detects high singleton ratio in checkerboard"`
---
### Requirement: Package provides edge analysis
The package SHALL analyse edges between adjacent pixels and return sharpness metrics.
#### Scenario: Solid image has zero hard edges
- **WHEN** `analyseEdges` is called on a solid-colour buffer
- **THEN** `hardEdges` is 0 and `meanEdgeDistance` is 0
**Verified by:** `src/analysis/edges/edges.test.ts::"solid image has zero hard edges"`
#### Scenario: 1×1 image has zero edges
- **WHEN** `analyseEdges` is called on a 1×1 buffer
- **THEN** `totalEdges` is 0, `sharpnessRatio` is 0, and `meanEdgeDistance` is 0
**Verified by:** `src/analysis/edges/edges.test.ts::"1x1 image has zero edges"`
---
### Requirement: Package provides colour quantisation
The package SHALL quantise RGBA pixel buffers to a target palette size using wrapped `image-q` algorithms.
#### Scenario: Gradient reduced to target colours
- **WHEN** `quantise` is called on a 32×32 gradient with `colours: 16`
- **THEN** `paletteSize` ≤ 16
**Verified by:** `src/quantise/quantise.test.ts::"reduces a gradient to target colour count"`
#### Scenario: All three algorithms supported
- **WHEN** `quantise` is called with `algorithm: "wuquant"`, `"neuquant"`, and `"rgbquant"`
- **THEN** each result has `paletteSize` ≤ 8
**Verified by:** `src/quantise/quantise.test.ts::"supports different algorithms"`
#### Scenario: Invalid dimensions throw structured error
- **WHEN** `quantise` is called with `width: 0`
- **THEN** it throws `QuantisationError` with code `QUANTISATION.INVALID_DIMENSIONS`
**Verified by:** `src/quantise/quantise.test.ts::"throws on invalid dimensions"`
---
### Requirement: Package provides spatial AA cleanup
The package SHALL merge small pools into nearest spatial neighbours below a colour distance threshold.
#### Scenario: Near-colour AA pixels merged
- **WHEN** `spatialCleanup` is called on an AA edge buffer with default options
- **THEN** the unique colour count drops from 3 to 2
**Verified by:** `src/spatial/cleanup/cleanup.test.ts::"merges near-colour AA pixels into spatial neighbours"`
#### Scenario: Pool size threshold respected
- **WHEN** `spatialCleanup` is called with `maxPoolSize: 0`
- **THEN** no merge occurs and colour count stays 3
**Verified by:** `src/spatial/cleanup/cleanup.test.ts::"does not merge pools above maxPoolSize"`
---
### Requirement: Package provides adaptive pipeline
The package SHALL orchestrate analysis → spatial cleanup → quantisation in a single call, selecting strategy based on complexity tier.
#### Scenario: Pixel-art skips quantisation
- **WHEN** `adaptivePipeline` is called on a solid-colour buffer
- **THEN** `quantisation` is `null` and `finalColours` is 1
**Verified by:** `src/pipeline/pipeline.test.ts::"skips quantisation for pixel-art"`
#### Scenario: Photographic images are quantised
- **WHEN** `adaptivePipeline` is called on a gradient
- **THEN** `quantisation` is not `null` and `finalColours` ≤ 256
**Verified by:** `src/pipeline/pipeline.test.ts::"quantises photographic images"`
#### Scenario: Spatial cleanup applied for vector-aa
- **WHEN** `adaptivePipeline` is called on a vector-aa image
- **THEN** `spatialCleanupApplied` is `true`
**Verified by:** `src/pipeline/pipeline.test.ts::"applies spatial cleanup for vector-aa images"`
---
### Requirement: Package validates options via Zod schemas
All public entry points that accept options SHALL validate them through exported Zod schemas before processing.
#### Scenario: Invalid options rejected by quantise
- **WHEN** `quantise` is called with `colours: 1`
- **THEN** it throws `QuantisationError` with code `QUANTISATION.PROCESSING_FAILED`
**Verified by:** `src/quantise/quantise.test.ts::"throws on invalid options"`
---
### Requirement: Package emits OpenTelemetry instruments
The package SHALL emit a counter and histogram for `quantise` and `adaptivePipeline` invocations.
#### Scenario: Instruments exist and are lazily initialised
- **WHEN** `quantise` is called
- **THEN** internal `getCounter()` and `getDuration()` functions return valid OTel instruments
**Verified by:** `src/quantise/quantise.test.ts::"reduces a gradient to target colour count"` (implicit via execution path)
---
### Requirement: Package defines stable error codes
The package SHALL declare error codes via `defineCodes("QUANTISATION", [...])` and throw `QuantisationError` with those codes.
#### Scenario: Every code is reachable
- **WHEN** invalid inputs are provided to `quantise` and `adaptivePipeline`
- **THEN** each of `INVALID_PIXEL_BUFFER`, `INVALID_DIMENSIONS`, and `PROCESSING_FAILED` is thrown at least once
**Verified by:** `src/quantise/quantise.test.ts::"throws on invalid dimensions"` AND `src/quantise/quantise.test.ts::"throws on mismatched buffer size"` AND `src/pipeline/pipeline.test.ts::"throws on invalid options"`
---
### Requirement: Package provides property-based tests
The package SHALL include fast-check property tests that verify invariants across random inputs.
#### Scenario: Palette size invariant
- **WHEN** random RGBA buffers and colour counts are quantised
- **THEN** `result.paletteSize` ≤ requested colours
**Verified by:** `src/property.test.ts::"quantise palette size <= requested colours"`
#### Scenario: Output dimensions invariant
- **WHEN** random RGBA buffers are quantised
- **THEN** output `width`, `height`, and `pixels.length` match input
**Verified by:** `src/property.test.ts::"quantise output has same dimensions as input"`
---
### Requirement: Package includes benchmarks
The package SHALL include mitata benchmarks for `quantise`, `analyseComplexity`, and `adaptivePipeline`.
#### Scenario: Benchmark file exists and compiles
- **WHEN** the benchmark file is inspected
- **THEN** `src/__benchmarks__/quantisation.bench.ts` exists and imports the public API
**Verified by:** manual inspection of `src/__benchmarks__/quantisation.bench.ts`