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.
Latest release: 1.8.0 — May 31, 2026
CharData 1.8.0
CxF/X-3 (Color Exchange Format, ISO 17972-3)
- Read CxF/X-3 as a third characterisation data format alongside CSV and CGATS/IT8. Prefix-agnostic XML parsing maps
Objectname → sample label,ColorCIELab→ L\*a\*b\*,ReflectanceSpectrum→ spectral columns, andColorCMYK/ColorCMYKPlusN/ColorRGB→ device colorants. - Write/Export a full CxF/X-3 document (device colorants + CIELab + spectrum + a
ColorSpecificationderived from the current illuminant / observer / M-condition).
Measurement-only datasets
Datasets with L\*a\*b\* and/or spectral data but no device colorants (common in CxF, also valid for CGATS/CSV) now load and support:
- the 3D L\*a\*b\* point cloud (sample label on hover),
- the 2D gamut-slice point cloud (projected, no boundary — like image-derived clouds),
- the data table with a sample-label column,
- Compare matched by sample label.
Gamut shell, Extract, Tone Value, Estimate, and G7 validation are unavailable for these (no device→colour relationship to model).
Other
- New UI strings localised across all 12 languages (+ translation matrices); fixed the measurement-only notice so it re-localizes live on language change.
- Refreshed the APTEC_CMYKOGV_Coated_LinearCTV_2025_M1 standard dataset (corrected CGATS header field/set counts).
- Manual and in-app help updated.
Older releases
1.7.3 — May 23, 2026
The ICC-vs-ICC branch of Compare was previously hardcoded to 4-channel
CMYK — two NCLR profiles refused with "ICC vs ICC comparison requires
CMYK profiles." That guard also masked a latent alignment bug
(#2): the
source-order remap used a fixed CMYK index, so any future NCLR support
would have silently mis-aligned non-CMYK inks.
This release lifts the restriction for the well-defined case, and
replaces the "CMYK only" error with a targeted message for the cases
that remain ill-defined.
What changed
------------
In runCompareWithIcc, the both-ICC branch now:
- Requires both profiles to expose identical ordered colorant lists
(e.g. two CMYKOGV proofers). When they don't match, the comparison is
refused with a message that names both lists.
- For 4-channel CMYK, continues to evaluate against the standard
IT8.7/5 patch set (unchanged).
- For any other matching colorant list, evaluates against a generated
N-channel patch set: paper white, single-ink ramps, and a two-ink
overprint grid that thins with channel count to keep patch counts
bounded (N=5 → 591 patches, N=7 → 666, N=10 → 821, N=15 → 1831).
- Drops the previously hardcoded
['CYAN','MAGENTA','YELLOW','BLACK']
source-order constant in favour of a single ICC-slot lookup — the
deep-equal guard makes that sufficient across every branch.
Wording
-------
icc_vs_icc_cmyk_only→icc_vs_icc_colorant_mismatchwith{a}/
{b} placeholders for the two colorant lists.
- Translations refreshed in all 12 supported UI languages (en, fr, de,
it, es, pt-PT, pt-BR, zh-CN, zh-TW, ja, ko, sv) and in the canonical
translations/Eng-*.xlsx spreadsheets. Eng-Pt.xlsx and
Eng-Zh.xlsx pick up the previously-empty pt-BR / zh-TW columns for
this row in passing.
Manual
------
MANUAL.md §5 (Compare mode) bullet for ICC vs ICC rewritten to
describe the colorant-list requirement and the CMYK / NCLR patch sets.
Closes #2.
1.7.2 — May 21, 2026
The Image tab is now always visible — Explore as well as Compare — and the
3D plot and 2D slice can render the image trace on its own, with no
Dataset A loaded, whenever the image carries an embedded ICC profile or
is a Lab TIFF. Removing the image (or unloading the last dataset) hides
both plots again.
Bind dropdown
-------------
The dataset-binding dropdown now adapts to what is actually bindable:
- If the image has an embedded ICC profile, empty dataset slots are
omitted entirely — only the embedded entry and any loaded datasets
appear.
- If the image has no embedded profile but at least one dataset is
loaded, empty slots still appear with a - placeholder so the slot
positions remain visible.
- If the image has no embedded profile and no datasets are loaded,
the dropdown is empty and disabled.
Plot defaults
-------------
- 2D slicer Show data points now defaults to ON (the prior default
hid the patch cloud, which is the most common reason people open the
slicer in the first place).
Wording
-------
- Bind-error message reworded for the new behaviour:
Dataset {slot} is not loaded. → No color dataset loaded.
- Translations refreshed in all 12 supported UI languages
(en, fr, de, it, es, pt-PT, pt-BR, zh-CN, zh-TW, ja, ko, sv) and in
the canonical translations/Eng-*.xlsx spreadsheets.
Manual
------
MANUAL.md §5.4 (Image gamut) updated to reflect the new always-visible
tab, the dropdown rules, and the image-only plotting case. The §4.4
slicer table notes the new Show data points default.
🤖 Generated with Claude Code
1.7.1 — May 21, 2026
Adds an Image tab in Compare mode that loads a real image file and plots its
colour gamut alongside the loaded Dataset A / Dataset B traces in the 3D plot
and 2D slice. Colorimetry is derived from the image's embedded ICC profile
when one is present, and falls back to channel-count inference otherwise.
Lab TIFFs plot in their native L*a*b* coordinates with no transformation.
Supported image formats
-----------------------
- PNG Embedded iCCP profile honoured (zlib-inflated via
DecompressionStream); Gray and RGB via Canvas.
- JPEG Embedded APP2 ICC profile honoured. Gray/RGB via Canvas; CMYK
decoded via UTIF's bundled JpegDecoder (PDF.js lineage), which
handles Adobe APP14 YCCK and emits CMYK in normal orientation.
- TIFF Embedded ICC profile (tag 34675); 8 / 16-bit Gray, RGB, CMYK,
and NCLR. PhotometricInterpretation 8 / 9 (CIELAB / ICCLab) is
auto-detected and plotted as Lab.
- BMP Indexed and 16 / 24 / 32-bit decoded as RGB via Canvas. V5 ICC
tag not yet read.
- GIF Palette → RGB via Canvas; first frame only for animated GIFs.
(GIF has no embedded ICC support in the format.)
- PDF Photoshop-generated PDFs only — strict 'Adobe Photoshop' check
on /Info Producer or Creator. Single-page; first image XObject
extracted. Linearized PDFs are walked via the /Prev xref chain.
Filters: /DCTDecode (incl. CMYK via UTIF) and /FlateDecode with
TIFF Predictor 2 or PNG predictors (10-15). Embedded ICC via
/ICCBased on the image, OR via page-level /DefaultGray,
/DefaultRGB, /DefaultCMYK colour-space override on a Device*-
tagged image (PDF spec §8.6.5.6).
Embedded ICC profile dropdown
-----------------------------
When an image carries an embedded ICC profile, the bind dropdown prepends an
'Embedded: <profile name>' entry and selects it by default. Conversion runs
the image's device pixels through the embedded profile via lcms2 (Absolute
Colorimetric) directly — no dataset binding required, and no family/channel
compatibility check applies (the profile is the image's colour space by
construction). The user can still pick Dataset A or B with the existing
compatibility check.
UI
--
The Image section sits above the 3D plot in Compare mode (collapsed by
default; hidden in Explore mode). Dark-mode styling matches the existing
plot / slice sections. Status messages now render outside the
display:none image-info wrapper so format-unsupported / PDF-rejected /
file-too-large errors are visible regardless of decode success.
Plumbing
--------
- inflateAsync() thin wrapper over native DecompressionStream('deflate'),
used by PNG iCCP and PDF /FlateDecode (image streams +
/ICCBased ICC streams). Lifts the prior PNG iCCP stub
that returned null.
- Minimal PDF parser (~300 lines) — classic xref tables, indirect-ref
resolver, dict/array/string lexer, /Prev chain following. Handles
Photoshop's linearized output (xref at offset 116, /Prev to the small
auxiliary table at end of file).
- decodeCmykJpegViaUtif() shared CMYK decode for standalone .jpg files
and PDF /DCTDecode CMYK streams. CMYK thumb
preview via (1-C)(1-K), (1-M)(1-K), (1-Y)(1-K).
i18n
----
29 new image_* keys added to all 12 locale dictionaries and the nine
Eng-*.xlsx translation spreadsheets. image_format_unsupported lists every
supported format. image_err_cmyk_jpeg removed (CMYK JPEGs are now
supported). check-translations audit clean.
Server
------
CSP img-src extended to allow blob: so URL.createObjectURL() works for
image thumbnails. script-src unchanged. The icctools postMessage handoff
is unchanged.
Docs
----
MANUAL.md §5.4 documents the format table, Photoshop-PDF scope, and the
embedded-ICC parity across formats.
Post-tag translation polish
---------------------------
Three follow-up commits address language-switch gaps discovered after the
initial 1.7.1 tag was cut. Re-tagged in place so 1.7.1 is the single
shipping artifact for the entire image-gamut feature line.
- Image tab strings written imperatively (colour-space line, points
summary, status messages, "Embedded: …" dropdown entry) now re-render
on language change via renderImageInfo() / repopulateImageBindDropdown()
/ bindImageToCurrentSelection() inside onLangChange().
- 18 previously untranslated strings ported to t(): file-select handles,
Contact button, file-card delete tooltip, rendering-intent dropdown
(label + 4 options) and ICC summary panel labels, Settings language
"System default" entry, ICC load/parse error prefixes, popup-blocked
alert, file-load failure alerts. New data-i18n-aria attribute handler
added to applyTranslations() for aria-labels.
- Estimate tab "ICC A2B Evaluation" / "ICC Output", Settings language
"System default (…)", and Compare table no-match error message now all
re-translate on language switch.
- scripts/generate-help.js TOC updated to include 5.4 Image gamut so the
in-app Help page links to the section.
- All translation spreadsheets (nine Eng-*.xlsx) carry the new rows;
scripts/check-translations.js reports zero drift.
Image detail (Coarse / Standard / Fine)
---------------------------------------
The Image tab now exposes an "Image detail" dropdown driving the
spatial-bin and dedupe-radius parameters used by the gamut pipeline.
Persists per browser via localStorage; Standard is the default.
- Coarse bins=900 radius=2.0 fastest, sparser trace
- Standard bins=2025 radius=1.0 default
- Fine bins=5000 radius=0.5 denser trace, slower
Changing the level re-runs binImage + dedupeRadial on the cached decoded
pixels and re-binds — no source-file re-decode. Four new image_detail_*
i18n keys added to all 12 locale dictionaries and the nine Eng-*.xlsx
spreadsheets. The opening of MANUAL.md now also calls out the icctools
companion and the in-app Launch editor hand-off.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
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
escapeHtmlbefore being interpolated into the slot panel'sinnerHTML. 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. launchIccEditorpostMessagetargetOriginis now pinned to the concrete icctools origin (http://localhost:5173in dev,location.originin prod) instead of'*'. Profile bytes can no longer be delivered to an unexpected origin if the popup is navigated cross-origin betweenwindow.openand theicctools:readyreply.- Translation strings documented as trusted HTML near the
t()definition, so future maintainers knowel.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.
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:5173deploy). - 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, awaitsicctools:ready, replies with{type:'icctools:load', filename, bytes}. One-way; icctools owns the editing session. - helmet COOP relaxed from
same-origintosame-origin-allow-popupsso cross-origin popups in local dev retainwindow.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 aspublic/wasm/icc-viewer.{mjs,wasm} chardata-gamut.wasmkeeps 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
acspmagic 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
colorantTableTagso real ink names ("Cyan", "Orange", "Violet"…) surface in the UI instead of genericCH1..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
escapeHtmlapplied 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 inserver.js, max-input-size guard.
Operations
- New
/healthendpoint returning200 okfor 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
wslinterop, 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-driftissue 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: falsewhen disabled, notopacity: 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.
Get notified about new releases
We’ll email you when a new major or minor chardata release ships. Patch releases are skipped.
✓
Thanks — we’ll be in touch.
Once approved you’ll receive a welcome email. After that, you’ll only hear from us when a new chardata release ships.
profiletool
In-browser ICC profile inspector & editor
A browser-based companion to chardata for inspecting and editing ICC profile internals — headers, tags, and type-specific data. Runs entirely client-side; no uploads. Free to access and use.