Color science · Software strategy · Expert consulting

Technical expertise at the intersection of color and code

XYZ Lab helps companies navigate color technology and software strategy — trusted by product teams and legal teams when the details really matter.

Explore chardata →

Color technology

Color science consulting, characterization data analysis, G7 validation, and spectral workflows for print and digital.

Software strategy

Engineering leadership, product strategy, and development consulting for teams building complex technical products.

Expert witness

Technical expert services for patent litigation and IP disputes in color technology and software — trusted when the details really matter.

chardata

Free color-data explorer

An interactive tool for inspecting and comparing CGATS and CSV datasets — with spectral plotting, G7 validation, and colorimetry generation. Built as a Node.js app. Free to access and use.

✓ Free to use Open source
Latest release: 1.6.1 — May 15, 2026

Security patch following an audit of the 1.6.0 launch-editor handoff. No new features; defence-in-depth fixes only.

Fixes

  • CGATS validation warnings/errors are now wrapped in escapeHtml before being interpolated into the slot panel's innerHTML. All current messages are literal/numeric so this isn't exploitable today; the change closes the door for a future contributor wiring user-derived text into the validator output.
  • launchIccEditor postMessage targetOrigin is now pinned to the concrete icctools origin (http://localhost:5173 in dev, location.origin in prod) instead of '*'. Profile bytes can no longer be delivered to an unexpected origin if the popup is navigated cross-origin between window.open and the icctools:ready reply.
  • Translation strings documented as trusted HTML near the t() definition, so future maintainers know el.innerHTML = t('foo') callers depend on this invariant.

Companion icctools changes

A parallel hardening commit went out on icctools (https://chardata.colourbill.com/profiletool/) covering: a 256 MB input-size cap on the postMessage launch handler, an origin allowlist on ?source=chardata, a meta CSP locking connect-src to 'self', a parsed-profile cache so describeTag doesn't re-parse on every tag-detail open, and an XML pre-scan that rejects <!DOCTYPE / <!ENTITY declarations to defeat billion-laughs attacks via IccLibXML's XML_PARSE_HUGE flag.

See commits 25ee853 (chardata) and 93a82bd (icctools) for details.

Older releases
1.6.0 — May 15, 2026

Highlights

  • Launch editor button added to the ICC profile viewer overlay. Opens icctools in a new tab with the current profile pre-loaded — useful for inspecting tag XML/JSON or making round-trip edits.
  • Profile bytes are handed off in-browser via postMessage. No upload, no server round-trip.
  • icctools moved to a new home: https://chardata.colourbill.com/profiletool/ (replaces the old :5173 deploy).
  • icctools redressed with a desktop/mobile responsive layout matching chardata's ≤720 px breakpoint.

Internal

  • Cross-app launch protocol: chardata opens icctools with ?source=chardata, awaits icctools:ready, replies with {type:'icctools:load', filename, bytes}. One-way; icctools owns the editing session.
  • helmet COOP relaxed from same-origin to same-origin-allow-popups so cross-origin popups in local dev retain window.opener. Prod is same-origin so the change is a no-op there.
  • i18n + 9 translation xlsx files updated with icc_launch_editor.
1.5.0 — May 14, 2026

ICC profile viewer

Click Display File on an ICC profile slot to open an in-page Header + Tags viewer (lazy-loaded ~730 KB IccProfLib WASM; chardata's cold load is unchanged). Each tag row expands inline to show its full Describe() output. Mobile-responsive: rows reflow to stacked cards at ≤720 px viewport — no horizontal scroll.

Highlights

  • New icc-viewer-wasm/ module compiled from IccProfLib (iccDEV), shipped lazily as public/wasm/icc-viewer.{mjs,wasm}
  • chardata-gamut.wasm keeps using lcms2 for transforms — no regression to existing ICC eval / mesh / slice
  • Pre-commit hook now rebuilds and stages the ICC viewer WASM whenever icc-viewer-wasm/ is touched
  • i18n strings added to all 12 supported languages and the 9 translation spreadsheets
  • Docs updated (CLAUDE.md, MANUAL.md, README.md)

Tag

1.5.0 — commit 851ccb0

1.4.0 — May 3, 2026

Highlights

ICC profile support

CharData now loads ICC profiles (.icc / .icm) alongside characterization datasets and treats them as virtual datasets evaluated through the profile's A2B (device-to-Lab) transform.

  • Header validation by acsp magic at offset 36; under- or over-sized files are rejected.
  • Supported device classes: Output (printer), Input (scanner), Display (monitor).
  • Supported color spaces: CMYK, CMY, RGB, GRAY, and generic NCLR (2..15 channel) profiles — including extended-gamut printer profiles such as 7CLR OGV (CMYK + Orange/Green/Violet).
  • For NCLR profiles the wrapper reads the colorantTableTag so real ink names ("Cyan", "Orange", "Violet"…) surface in the UI instead of generic CH1..CHn.
  • All four ICC rendering intents are selectable; intents not present in the profile are greyed out.
  • Vendored lcms2 2.19 for parsing and transforms (compiled into the WASM module).

Per-slot Rendering Intent control

Each slot holding an ICC profile gains a Rendering Intent dropdown (default Absolute Colorimetric, falling back to the first intent the profile supports). Switching intent re-runs the full pipeline:

  • 3D gamut shell + IT8.7/5 patch cloud
  • 2D gamut slice
  • Comparison table + summary statistics
  • Tone Value chart (Y-axis range invalidated and re-fit)
  • Estimate prediction

The control is hidden whenever the slot holds char data instead of a profile.

File Select pane redesigned

  • Two collapsible sections — Characterization data and ICC Profiles — with persistent expand/collapse state.
  • Drag-resizable width (vertical grip dots on the right edge) with width remembered across sessions.
  • One picker button + drag-and-drop into the pane; CharData sniffs each file's content and routes it to the correct section automatically.

Comparison table — unified and expanded

The Compare table now uses a single renderer for all four data/profile combinations (data vs data, data vs ICC, ICC vs data, ICC vs ICC) with identical column structure and decimal-aligned colorant cells throughout.

The Difference section now includes:

| Column | Meaning |

|---|---|

| ΔE | Selected ΔE method |

| ΔL\* | Lightness difference (B − A) |

| ΔC\* | Chroma difference (B − A) |

| ΔH\* | Geometric hue distance (chroma-weighted, via the selected ΔE method) |

| Δh\*(deg) | Hue-angle difference, signed shortest path in (−180°, +180°] |

Bold rows are new in 1.4.0.

Tone Value with ICC

For ICC slots, CharData synthesises a primary tone ramp by sampling the profile at fixed tint percentages (0, 5, 10, 15, 20, 25, 30, 40, 50, 60, 70, 80, 90, 95, 100) along each colorant. ICC slots have no spectral data, so only CTV works for them; Murray-Davies traces from ICC slots are skipped.

Estimate

  • Char data: polynomial fit (existing behaviour).
  • ICC profiles: prediction uses the profile's A2B transform directly — no fit step required.

3D plot defaults differ by file type

  • Char dataset → shell off / points on (existing behaviour).
  • ICC profile → shell on / points off (continuous transform, sample cloud less meaningful).

Per-slot checkboxes still let you override either default.

Translations

Refreshed across 11 languages (English, French, German, Italian, Spanish, Portuguese PT/BR, Chinese Simplified/Traditional, Japanese, Korean, Swedish) and the 11 spreadsheet sources under translations/. New strings cover the File Select sections, ICC type label, and ICC error message.

A reusable audit script (scripts/check-translations.js) was added for catching drift between the in-app I18N dict and the canonical Eng-*.xlsx sources.

Documentation

[MANUAL.md](MANUAL.md) and the in-app help (public/help.html) updated with new sections covering ICC profiles, the redesigned File Select pane, the Rendering Intent control, the unified comparison table columns, and the ICC paths through Tone Value and Estimate.

[CLAUDE.md](CLAUDE.md) refreshed to document the WASM/help-html pipeline, pre-commit hook, deployment, and i18n flow for future contributors.

Security

  • escapeHtml applied to file-name and colorant-name interpolations in the Compare and Explore renderers.
  • 32 MB upper bound on ICC profile size to protect the WASM heap from oversized inputs.
  • Deferred items tracked in [SECURITY-FOLLOWUPS.md](SECURITY-FOLLOWUPS.md) — escapeHtml sweep of pre-existing sinks, CSP header in server.js, max-input-size guard.

Operations

  • New /health endpoint returning 200 ok for lightweight uptime monitors. Avoids fetching the full SPA on every check.

Build / infra

  • Pre-commit hook now detects whether it is running inside WSL or on a Windows shell that needs the wsl interop, so contributors can commit from either environment.

Known limitations

  • Comparing two NCLR profiles to each other still aligns colorants assuming CMYK ordering; non-CMYK inks (Orange/Green/Violet etc.) may be dropped or misaligned in the table. Tracked as #2. ICC-vs-CGATS and CMYK-vs-CMYK ICC compares are unaffected.

---

Full diff: 1.3.1...1.4.0

1.3.1 — April 30, 2026

Changes

  • Vendor Plotly 2.27.0: plotly-2.27.0.min.js (3.5 MB) is now served locally instead of from cdn.plot.ly, eliminating SRI breakage when the CDN silently updates bytes for the same version URL.
  • Weekly CDN hash monitor: A GitHub Actions workflow runs every Monday and opens a plotly-cdn-drift issue if cdn.plot.ly diverges from the vendored file, so upgrades can be reviewed and applied deliberately.
1.2.2 — April 29, 2026

Fixes the 'Choose char data file' button being greyed-out and unresponsive in mobile layout (and desktop browser resized to ≤700px).

Root cause: the desktop CSS rules for both drawer blades set z-index:200 after the mobile media query's z-index:1000 in the stylesheet. Equal-specificity rules resolve by source order, so the later desktop value always won — leaving the blades behind the backdrop (z-index:999) on mobile. The backdrop visually darkened the button and consumed all clicks (closing the blade instead of opening the file picker).

Fix: added !important to z-index:1000 in the mobile media query for both #file-select-blade and #blade.

1.2.1 — April 28, 2026

Fixing some issues with multi-localization (incorrect, missing translations, esp Chinese).

Fixing some minor bugs wrt display when resizing browser window.

v1.2.0 — April 28, 2026

What's new since v1.1.5

Modeling

  • Weighted polynomial model — new Settings option (Model → Weighted on/off). Anchors primaries, secondaries, and white point with higher influence during fitting, producing a gamut shell that passes through measured boundary conditions.
  • Model-based gamut for both slots — in Compare mode, both datasets A and B are now fitted with a polynomial model. The 3D gamut shell and 2D gamut slice use the model-synthesised boundary for both slots.

2D Gamut Slice

  • Bandwidth control removed — the model isoline extraction replaces the raw data bandwidth filter in all modes.

3D Plot UX

  • Controls panel moved below the Plotly canvas (no longer overlaid on the plot).
  • Computing indicator repositioned to top-right, clear of dataset controls.
  • Computing indicator now shown for all Plotly update operations (shell toggle, points toggle, color mode, opacity, lighting).
  • Fixed ghost shell rendering: shell traces now use visible: false when disabled, not opacity: 0, preventing spurious alphahull computation.
  • Fixed vertex color count mismatch in recolorPlot when model boundary cloud and raw data have different point counts.

Settings

  • New Model section (between Spectral → LAB and Display).
  • Filter Duplicates now defaults to No.

Localisation

  • Multi-language support for all new UI strings (EFIGS, CJK, Portuguese).
1.2.0 — April 28, 2026

Updated modeling to include ability to extra-weight primaries and secondaries for gamut calculation.

Updated 3d plot of gamut and gamut-slicer to use model, rather than convex hull of characterization data.

Added basic (EFIGS+P+CJK) multi-localized support. (no update for help file yet)

1.1.5 — April 22, 2026

Added Murray-Davies and CTV generation

1.1.4 — April 17, 2026

Replaced check for hard presence of CMYK device colorants - this is not necessary when just examining data files, especially ECG ones, which may or may not have CMYK.

1.1.3 — April 17, 2026

Improved performance of gamut shell visualization through pre-sorting of points.

Added Estimate section using simple modeling.

1.1.2 — April 14, 2026

Modified behaviour of gamut slicer visibility.

Modified behaviour of show/hide gamut shells in 3d Plot.

Added dark mode.

Modified data table behaviour.

Many changes to 3d plot to improve performance.

1.1.1 — April 14, 2026

Added functionality to display gamut shell slices.

1.1.0 — April 14, 2026

Added gamut shell visualization capability for both Explore and Compare modes.

Cleaned up some usability issues, especially for mobile.

1.0.0 — April 13, 2026

First public release of chardata app.

Chardata is a node.js app designed to do simple inspection and comparison of color characterization data sets created using the CGATS file format (CSV also accepted), with or without spectral information.

If colorimetric information is missing, Chardata will use the spectral info to generate colorimetry.

Density information is discarded for now.

Plotting and comparison functions are available.

G7 validation is available with a detailed report.

Spectral plotting of single data points is also available.

Color blog: posts about chardata and color science ↓

Working on something that needs deep color or software expertise?

Whether it’s a product challenge, a technical decision, or a legal matter — let’s talk.