Initial Commit
This commit is contained in:
@@ -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`
|
||||
Reference in New Issue
Block a user