Launch OpenGPEX

Color Pipeline

How OpenGPEX manages color spaces across the entire image lifecycle — from import through compositing to export — using a centralized strategy matrix that ensures zero color drift and full ICC profile round-trip fidelity.


Architecture: Centralized Strategy Routing

All color-space decisions are concentrated in a single ColorPipeline.ts strategy matrix. Format handlers, compositors, and renderers query this matrix and execute instructions — they never make autonomous color decisions.

Source File → [FORMAT_COLOR_STRATEGY] → detectedCS → [IMPORT_PIPELINE] → Frame.colorSpace
                                                                              │
                                                                    ┌─────────┴─────────┐
                                                                    ▼                   ▼
                                                            [WORKING_PIPELINE]    sourceBlob?
                                                            composite/display         │
                                                            /export                   ▼
                                                                              canUseFastExport()
                                                                              YES → direct return
                                                                              NO  → strategy encode

Core Design Principles

  1. Single source of truth — Modify one strategy entry to change behavior globally
  2. Only two runtime valuesFrame.colorSpace is always 'srgb' or 'display-p3'
  3. Handlers are pure executors — They switch/case on strategy instructions, zero if/else judgment
  4. Renderer-agnostic — Color management is pipeline logic, not tied to Canvas 2D / WebGL / WebGPU

Two-Layer Strategy System

Layer 1: Format Strategy (by file format)

Decides how to read color metadata and whether to retain the original source blob:

Format Reads ICC Supports ICC Embed Source Blob Retention
PNG On fidelity loss
JPEG On fidelity loss
WebP On fidelity loss
AVIF On fidelity loss
TIFF On fidelity loss
RAW Always
HEIC ❌ (decode only) Never
BMP/GIF/SVG Never (fixed sRGB)

Layer 2: Color Space Strategy (by detected color space)

Decides what happens to pixels at each pipeline stage:

Source ColorSpace Frame.colorSpace Conversion Notes
sRGB srgb None Zero-cost passthrough
Display P3 display-p3 None Native wide-gamut
Adobe RGB display-p3 3×3 Matrix P3 covers ~98% of AdobeRGB
ProPhoto RGB display-p3 3×3 Matrix ~30% super-gamut clamped
CMYK srgb ICC Engine (vips) Full Little CMS transform
Custom ICC srgb ICC Engine (vips) sourceBlob preserves original

Wide-Gamut (Display P3) Support

OpenGPEX natively supports Display P3 as a working color space:

  • Import: P3 source files set Frame.colorSpace = 'display-p3' directly; AdobeRGB/ProPhoto are matrix-converted to P3 at import time (one-shot, ~4ms/1080p)
  • Composite: Canvas 2D context created with { colorSpace: 'display-p3' } — zero per-layer conversion overhead
  • Display: Auto-detects P3 hardware via color-gamut: p3 media query; falls back to sRGB + matrix clamp on non-P3 displays
  • Export: P3 pixels encoded directly + ICC Profile embedded (PNG/JPEG/WebP/AVIF/TIFF)

Import-Time Conversion (Choice A)

AdobeRGB/ProPhoto pixels are converted to P3 once during decode(), not at composite time. This means:

  • Zero composite overhead — No per-layer, per-frame conversion during editing
  • Full export fidelitysourceBlob preserves the original file for lossless round-trip
  • Simple architecture — Compositing only ever sees sRGB or P3 pixels

Export Color Management

Fast-Path Export

When conditions are met (single layer + unedited + same format), the original sourceBlob is returned directly — preserving original ICC, bit depth, and color encoding with zero computation.

Normal Export Path

Three decisions are made at export time:

Decision Function Purpose
ICC Embed shouldEmbedIcc(format, frameCS, userOverride?) 3-layer decision: format capability × strategy default × user choice
Pixel Conversion resolveExportPixelConversion(frameCS, meta, embedIcc, format?) Determines if pixels need transformation
Format Downgrade getExportStrategy(frameCS, targetFormat?) Auto-downgrades P3→sRGB for formats that don't support P3 (BMP/GIF)

ICC Embed Three-Layer Model

Layer 1: Format capability      (BMP/GIF → never; PNG/JPEG/WebP/AVIF/TIFF → allowed)
Layer 2: Strategy default        (sRGB: true; P3: true — matches Photoshop/Lightroom)
Layer 3: User override           (export UI checkbox, if provided)

Color Pipeline Infrastructure

Matrix Conversion Engine

Pure TypeScript 3×3 linear matrix transforms (zero external dependencies):

Transform Use Case Performance (1080p)
sRGB ↔ P3 Most common ~3ms
AdobeRGB → P3 Import ~3ms
ProPhoto → P3 Import (with Bradford D50→D65) ~4ms

TRC (Transfer Curve) Handling

  • sRGB ↔ linear conversion via pre-computed LUT (~2ms/4K)
  • Linear-light compositing for physically-correct blend modes
  • Filter TRC auto-insertion (blur executes in linear, auto-converts back)

ICC Profile Support

Capability Technology Location
ICC v2/v4 parsing Pure TypeScript core/files/icc.ts
Full ICC transform wasm-vips + Little CMS (Worker) file-io.ts
ICC injection (PNG) iCCP chunk builder handlers/png/writers.ts
ICC injection (JPEG) APP2 marker injection handlers/jpeg/jfif.ts
ICC injection (WebP) RIFF ICCP chunk (custom) handlers/webp/riff.ts
ICC injection (TIFF/AVIF) vips iccTransform Worker (vips)

Format × Color Space Matrix (Summary)

Source Import Action Frame.colorSpace Export Behavior
sRGB anything Passthrough srgb sRGB direct output
P3 anything Passthrough display-p3 P3 + ICC embed
AdobeRGB Matrix → P3 display-p3 P3 + ICC (or sourceBlob fast-path)
ProPhoto Matrix → P3 display-p3 P3 + ICC (or sourceBlob fast-path)
CMYK ICC → sRGB srgb sRGB + optional ICC restore
Custom ICC ICC → sRGB srgb sRGB + optional ICC restore

Comparison with Desktop Applications

Aspect Photoshop / GIMP OpenGPEX
Working space User-configured (any ICC) Auto-detected (sRGB or P3)
Color engine Little CMS (compiled in) Pluggable: BuiltinEngine (matrices) + VipsEngine (Little CMS)
Per-pixel tagging Yes (babl in GIMP) No — entire Frame shares one space
Conversion trigger Every op declares format → auto-insert Import-time one-shot + strategy matrix
Export ICC Optional, user-configured Three-layer auto-decision
Wide-gamut preview Requires calibrated workflow Auto-detects P3 hardware

Known Limitations & Future

Limitation Cause Resolution
ProPhoto ~30% super-gamut clamped Canvas 2D only supports P3/sRGB WebGPU (float16 textures)
8-bit canvas precision OffscreenCanvas limitation WebGPU (float16/float32)
No soft-proofing No display transform layer yet Phase D: DisplayStrategy extension
No user-initiated space conversion Frame.colorSpace currently immutable Planned as undoable command

Last updated: 2026-08-08