Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

Контекстный компилятор цветовых токенов для дизайн-систем.

`LCS` означает **Labpics Colors Space**, `LPC` — **Labpics Perceptual Contrast**.
Это собственные концепции Labpics; текущие CAM16/Oklab/APCA-shaped компоненты не
являются их полным определением и не доказывают perceptual superiority.

Comment thread
lemone112 marked this conversation as resolved.
Lab Colors принимает конфиг клиента, компилирует его в `NamedRoleTable` и решает всю таблицу для одного локального фона и темы. Зависимые роли, уже представленные специальными рецептами, используют фактически полученные композиты. Браузерные помощники применяют результат, перепроверяют его при изменении окружения и при необходимости запускают новый resolve.

```text
Expand Down Expand Up @@ -225,7 +229,7 @@ anchors

- Нормативный floor применяется только там, где его требует контракт клиента или компонента.
- Core не определяет размер текста, essentialness, disabled/decorative status по имени роли.
- Экспериментальный LPC/APCA-shaped или appearance-результат не меняет WCAG pass/fail.
- Экспериментальный APCA-shaped компонент текущего LPC или appearance-результат не меняет WCAG pass/fail.
Comment thread
lemone112 marked this conversation as resolved.
Outdated
- Для финальной пары sRGB8 новый `wcag22-srgb8-contrast-v1` принимает явно объявленный критерий и возвращает строгий `Pass | Fail`; профиль, Q55-артефакт и full-domain proof входят в релиз.
- Старое поле `wcagRatio` остаётся compatibility-диагностикой текущего resolver/runtime и не может автоматически рекламироваться как результат нового evaluator-а.
- Цвет не должен быть единственным носителем смысла; текст, иконка и форма принадлежат компоненту.
Expand Down
6 changes: 3 additions & 3 deletions crates/labcolors-core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,9 @@ offline-операции. Все поверхности используют о
вычислителя или компилятора возвращаются типизированными ошибками; частичного
или запасного результата нет.

Полные проверки формул `C×E`, случая `E=0`, повторов ID, ресурсных отказов и
запрета частичного результата перечислены в разделе «Конечная компиляция
выполнимости WCAG 2.2» [карты верификации](../../docs/verification-map.md).
Формулы `C×E`, случай `E=0`, повторы ID, ресурсные отказы и запрет частичного
результата исполняются непосредственно в `src/wcag22_feasibility_tests.rs`,
`tests/wcag22_feasibility.rs` и `tests/wcag22_explicit_feasibility.rs`.

```rust
# #[cfg(feature = "wcag22-feasibility")]
Expand Down
6 changes: 3 additions & 3 deletions crates/labcolors-core/src/golden_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -222,9 +222,9 @@ fn cam16_matches_colour_science_dim_surround() {
/// console.log(APCAcontrast(y(0),y(255)));" // 106.04066682868873
/// ```
///
/// Grey-on-grey isolates the Helmholtz-Kohlrausch term out of the metric
/// (luminance is fed directly), so this validates the curve alone.
/// Константы: APCA SAPC-8 версии 0.0.98G-4g; метрика называется LPC, не APCA.
/// Grey-on-grey bypasses the Helmholtz-Kohlrausch path because luminance is fed
/// directly. This validates the frozen candidate curve arithmetic only; it does
/// not validate APCA conformance, complete LPC or readability.
type ContrastGolden = (f64, f64, f64);
const ACHROMATIC_CONTRAST: [ContrastGolden; 13] = [
(0.0, 1.0, 106.04066682868873), // #000000 on #ffffff (BoW max)
Expand Down
12 changes: 9 additions & 3 deletions crates/labcolors-core/src/lcs.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
//! Current point representation used while **Labpics Colors Space (LCS)** is
//! being reduced to one context-bound coordinate contract. The stored
//! CAM16-UCS/Oklab views are implementation inputs, not independent editable
//! definitions of LCS and not a claim of uniform perceptual attributes.

use crate::spaces::srgb::{hex_from_srgb, srgb_from_hex, srgb_to_xyz, xyz_to_srgb};
use crate::spaces::{cam16, cat16, oklab, vc::ViewingConditions};

Expand Down Expand Up @@ -92,9 +97,10 @@ impl LcsColor {
/// caller that already ran [`cam16::forward`] (e.g. [`crate::solve`]'s
/// `finish`) reuses that result instead of recomputing it.
pub(crate) fn from_cam16(j: f64, m: f64, h_cam: f64, h_ok: f64) -> Self {
// CAM16-UCS rescaling (Li et al. 2017, DOI 10.1002/col.22131): maps raw
// CIECAM16 J/M onto perceptually uniform J'/M' (J'=50 reads as
// half-lightness). Inverse in `to_xyz` via the same helpers.
// CAM16-UCS rescaling (Li et al. 2017, DOI 10.1002/col.22131). This is
// an exactly inverted coordinate transform used for colour-difference
// work; no individual J'/M' value is assigned a universal attribute
// meaning here. Inverse in `to_xyz` uses the same helpers.
let jp = cam16::ucs_j(j);
let mp = cam16::ucs_m(m);
let s = mp / (jp + 1.0);
Expand Down
10 changes: 5 additions & 5 deletions crates/labcolors-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,11 +77,11 @@ mod pair_label_tests;
#[cfg(test)]
mod r3_byte_identity_tests;

// External published reference vectors for the deepest colour-science layers
// (sRGB EOTF & matrices, Ottosson Oklab, CAT16/CIECAM16 adapt, Hellwig-2022 H-K,
// WCAG linearise). These reach `pub(crate)` transforms an integration test in
// `tests/` cannot see; the public-API-reachable vectors live in
// `tests/reference_vectors.rs`. See `docs/verification-map.md`.
// Reference checks for the deepest colour-science layers (sRGB EOTF & matrices,
// Ottosson Oklab, CAT16/CIECAM16 adapt, Hellwig-2022 H-K, WCAG linearise). These
// reach `pub(crate)` transforms an integration test in `tests/` cannot see; the
// public-API-reachable checks live in `tests/reference_vectors.rs`, with source
// and oracle scope beside each test.
#[cfg(test)]
mod reference_vectors_deep;

Expand Down
50 changes: 22 additions & 28 deletions crates/labcolors-core/src/lpc.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
//! Candidate components of **Labpics Perceptual Contrast (LPC)**.
//!
//! The current APCA-shaped and H-K paths are characterized implementation
//! components, not the complete LPC definition and not evidence that LPC
//! outperforms APCA. Readability admission is owned by a versioned evaluator
//! profile with declared typography, observer and context applicability.

use crate::spaces::srgb::{D65_WHITE, srgb_from_hex, srgb_to_xyz};
use crate::spaces::{cam16, cat16, vc::ViewingConditions};

Expand Down Expand Up @@ -174,15 +181,11 @@ fn y_hk_bisect(j_hk: f64, vc: &ViewingConditions) -> f64 {
(lo + hi) * 0.5
}

// Канонические константы перцептивного контраста из опубликованной формулы
// версии 0.0.98G-4g («4g»-набор SAPC-8). Имена в комментариях воспроизводят
// исходные идентификаторы, чтобы маппинг был аудируемым.
//
// Правовая позиция: Copyright (17 U.S.C. § 102(b)) не охраняет формулы и константы,
// только конкретное кодовое выражение. Данная реализация написана независимо;
// файлы репозиториев Myndex не копировались. Метрика называется LPC — не APCA,
// не APCA-совместима и не одобрена Myndex Research или Andrew Somers.
// Товарный знак «APCA» в публичных API-символах и названии метрики не используется.
// Константы candidate-кривой транскрибированы из опубликованного набора
// SAPC-8 0.0.98G-4g. Имена в комментариях воспроизводят исходные
// идентификаторы, чтобы маппинг был аудируемым. Эта транскрипция сама по себе не
// является APCA conformance, complete LPC или evidence читаемости; комментарий
// также не делает правового вывода о допустимости дальнейшего распространения.
//
// Это ЕДИНСТВЕННЫЙ ИСТОЧНИК ИСТИНЫ для кривой контраста: и прямой `contrast_core`,
// и обратный решатель (`crate::solve`) читают значения здесь.
Expand Down Expand Up @@ -308,27 +311,18 @@ pub(crate) fn soft_clamp_inv(clamped: f64) -> Option<f64> {
Some(y)
}

/// Perceptual-contrast core curve (asymmetric power contrast on luminance).
/// Candidate asymmetric power curve over a luminance-shaped scalar.
///
/// Faithful port of the published generic perceptual-contrast math — soft
/// black clamp, polarity-dependent power exponents, the minimum-luminance
/// gate, the low-contrast clip, and the polarity offsets. Fed the *same* input
/// luminance, the curve reproduces the reference; the absolute numbers agree
/// with the published APCA only at the endpoints (Y = 0 and Y = 1, e.g. black
/// on white ≈ `106.04`). For interior greys the luminance fed here is
/// `Y_hk`, not the reference's `Ys`, so LPC departs from the published APCA
/// on those: measured against `apca-w3` on the 8-bit grey axis the departure
/// stays within ~2.3 Lc (grey-on-grey pairs ≤ ~0.6 Lc, endpoints exact). On
/// near-neutrals the H-K term itself is ≈0 (M ≲ 1), so the interior departure
/// is dominated by the CAM16 lightness reconstruction inside `Y_hk`. A
/// deliberate, declared difference of the metric (the same `Y_hk` substitution
/// that makes LPC diverge from the reference on chromatic colours), not a
/// porting error.
/// The branches and constants mirror the frozen SAPC-8 0.0.98G-4g candidate:
/// soft black clamp, polarity-dependent exponents, a minimum-luminance gate,
/// low-contrast clipping and polarity offsets. Current call sites feed more
/// than one luminance definition, including an H-K/CAM16-derived scalar. That
/// composition is characterized implementation behavior, not APCA conformance,
/// complete LPC or evidence of glyph readability.
///
/// Константы: формула APCA SAPC-8 версии 0.0.98G-4g; метрика называется LPC,
/// не APCA, не одобрена Myndex Research. The achromatic alignment is locked by
/// `golden_tests::contrast_core_matches_reference_on_grey_axis`. The curve is
/// inverted by `crate::solve` to recover a foreground luminance from a target.
/// `golden_tests::contrast_core_matches_reference_on_grey_axis` pins only the
/// scalar curve arithmetic. The curve is inverted by `crate::solve` to recover
/// a foreground scalar from a target.
pub(crate) fn contrast_core(y_fg: f64, y_bg: f64) -> f64 {
let fg = soft_clamp(y_fg);
let bg = soft_clamp(y_bg);
Expand Down
5 changes: 3 additions & 2 deletions crates/labcolors-core/src/pair_label_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -279,8 +279,9 @@ fn pair_label_spec(
/// отгруженным фоном светлых labui-тем (классы совпадают в этой дизайн-
/// системе по факту фикстуры);
/// * `#101012` — отгруженный фон тёмных labui-тем (фикстура);
/// * `#767676` — опубликованная WCAG-граница серого (≈4.54:1 к белому,
/// см. `docs/verification-map.md`);
/// * `#767676` — опубликованная WCAG-граница серого (≈4.54:1 к белому),
/// напрямую закреплённая
/// `tests/reference_vectors.rs::wcag_published_ratios_via_public_api`;
/// * `#FFF4E0` — хроматический светлый witness: точная warning-поверхность
/// из graph-тестов (`appearance_graph_tests`);
/// * `#0000FF` — насыщенный хроматический угол куба (sRGB primary).
Expand Down
17 changes: 9 additions & 8 deletions crates/labcolors-core/src/reference_vectors_deep.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
//! External published reference vectors for the deepest colour-science layers.
//! Reference checks for the deepest colour-science layers.
//!
//! These pin the crate's transforms to CONTROL POINTS AND VECTORS PUBLISHED IN
//! STANDARDS / PEER-REVIEWED SOURCES, not to the crate's own output. They live
//! in-crate (not `tests/`) because the transforms they touch are `pub(crate)`
//! and invisible to an integration test. Public-API-reachable vectors are in
//! `tests/reference_vectors.rs`; the full map is `docs/verification-map.md`.
//! The checks combine published control points, independent transcriptions and
//! explicit identities. They live in-crate (not `tests/`) because the transforms
//! they touch are `pub(crate)` and invisible to an integration test.
//! Public-API-reachable checks are in `tests/reference_vectors.rs`; each test
//! below carries its own source and applicability boundary beside the assertion
//! it protects.
//!
//! Sources cited per test:
//! * IEC 61966-2-1:1999 — sRGB EOTF/OETF and primaries; also W3C CSS Color 4.
Expand Down Expand Up @@ -297,8 +298,8 @@ fn cam16_ucs_constants() {
"ucs_m not invertible at {m}"
);
}
// J'=50 reads as half-lightness only if the 1.7/0.007 pair is intact:
// published sanity value ucs_j(43.30..) ≈ 55.6 is a monotone lift, not 1:1.
// The rescale is not the identity: J=50 maps above 50. This checks only the
// published coordinate transform, not a human meaning for either number.
assert!(ucs_j(50.0) > 50.0, "UCS lightness lift must raise J");
}

Expand Down
10 changes: 5 additions & 5 deletions crates/labcolors-core/src/semantic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,11 +130,11 @@
//! The default undertone policy is [`RoleChroma::Curve`] (v2), derived from three
//! computable mechanisms rather than a flat ratio of the gamut:
//!
//! 1. **Constant perceptual colorfulness** — the chroma at each role's resolved
//! lightness is solved to a *constant* CAM16-UCS `M'` (`TINT_TARGET_MP`), not
//! a fixed fraction of the gamut maximum. Because UCS is perceptually uniform,
//! one constant holds the chroma in the lights and moderates it in the middle
//! fixing v1's inverted envelope (over-saturated middle, starved light end).
//! 1. **Constant CAM16-UCS coordinate** — the chroma at each role's resolved
//! lightness is solved to a constant `M'` (`TINT_TARGET_MP`), not a fixed
//! fraction of the gamut maximum. This is a characterized design policy that
//! holds chroma in the lights and moderates it in the middle; it does not turn
//! `M'` into a universal perceptual-colorfulness scale.
Comment thread
lemone112 marked this conversation as resolved.
//! 2. **Cusp-attracted hue** — the hue at each lightness is pulled toward the
//! local chroma cusp of the sRGB gamut, penalised for leaving the canonical
//! 286° (`cusp_attracted_hue`). The drift emerges from geometry; it is *not*
Expand Down
7 changes: 4 additions & 3 deletions crates/labcolors-core/src/spaces/cam16.rs
Original file line number Diff line number Diff line change
Expand Up @@ -241,9 +241,10 @@ fn forward_compute(xyz: [f64; 3], vc: &ViewingConditions) -> (f64, f64, f64) {
//
// J' = 1.7·J / (1 + 0.007·J), M' = ln(1 + 0.0228·M) / 0.0228.
//
// Maps raw CIECAM16 J/M onto perceptually uniform J'/M' (J'=50 reads as
// half-lightness). These four helpers are the SINGLE SOURCE OF TRUTH for the
// rescale: `lcs` stores J'/M', `lpc` decompresses back to raw J/M, and the
// These four helpers are the SINGLE SOURCE OF TRUTH for the exactly invertible
// CAM16-UCS coordinate rescale. They do not assign universal perceptual-
// attribute meaning to an individual J'/M' value: `lcs` stores the coordinates,
// `lpc` decompresses back to raw J/M, and the
Comment thread
lemone112 marked this conversation as resolved.
Outdated
// constants (`1.7`, `0.007`, `0.0228`) must never be re-typed inline anywhere
// else (previously duplicated across `lcs::from_xyz_with_hok`, `lcs::to_xyz`,
// and `lpc::y_hk_from_lcs`).
Expand Down
12 changes: 7 additions & 5 deletions crates/labcolors-core/tests/reference_vectors.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
//! External published reference vectors reachable through the PUBLIC API.
//! Reference checks reachable through the PUBLIC API.
//!
//! Companion to the crate-internal `reference_vectors_deep` (which reaches
//! `pub(crate)` transforms). Every vector here is a control point or worked
//! value from a STANDARD or PEER-REVIEWED SOURCE, asserted end-to-end through
//! the shipped surface. Full map: `docs/verification-map.md`.
//! `pub(crate)` transforms). The checks combine published control points,
//! independently transcribed formulae and explicit cross-boundary identities.
//! Each assertion owns its source and oracle boundary; crate-private companion
//! checks live in `src/reference_vectors_deep.rs`.
//!
//! Sources:
//! * W3C WCAG 2.1 §1.4.3 / §1.4.11 — relative luminance & contrast ratio.
Expand Down Expand Up @@ -262,7 +263,8 @@ fn dim_surround_shifts_lpc() {
// (byte-exact round-trip, proven for the core by `oklch::round_trip_is_byte_exact`).
// This file OWNS the seed set; the committed fixture is the artifact the JS test
// reads; `oklch_core_vectors_fixture_is_fresh` keeps it in lock-step with the
// live emitter. See `docs/verification-map.md`.
// live emitter, while `packages/colors/test/reference-vectors.test.mjs`
// independently decodes the committed strings back to the seed bytes.
// ═════════════════════════════════════════════════════════════════════════════

const FIXTURE_REL: &str = "/../../packages/colors/test/data/oklch-core-vectors.txt";
Expand Down
2 changes: 1 addition & 1 deletion crates/labcolors-wasm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -732,7 +732,7 @@ impl LabColors {
/// foregrounds against every sample of a varying backdrop (gradient / image /
/// bg-blur / glass); the dominant per-foreground CAM16 forward is background-
/// independent, so this shares it across all samples instead of recomputing it
/// per `recheckContrast` call — a measured ~2.6x on the multi-sample recheck.
/// per `recheckContrast` call.
///
/// Returns a flat, background-major `Float64Array`: sample `s`, foreground `i`
/// is at `(s * fgHexes.length + i) * 2` (`lc`) and `+1` (`wcagRatio`). The
Expand Down
11 changes: 6 additions & 5 deletions docs/decisions/0003-hk-scope.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,11 +87,12 @@ H-K остаётся там, где он про яркость (свечение

### 2. APCA/SAPC определён в домене экранной яркости `Ys`, без поправки H-K

APCA (метрика, которую движок реализует независимо под именем **LPC** — так же,
как WCAG определяет *relative luminance*) вычисляет контраст целиком в домене
**экранной яркости `Ys`**: линеаризация sRGB + взвешивание коэффициентами яркости.
Все эмпирические константы работают в этом домене; поправка Гельмгольца–Кольрауша
в алгоритме **не применяется**.
Опубликованная APCA-формула вычисляет контраст в собственном домене `Ys`:
channel power и взвешивание коэффициентами яркости. Текущий Lab Colors
независимо использует APCA-shaped curve как один кандидат-компонент **Labpics
Perceptual Contrast (LPC)**, но подаёт в readability-путь другую legacy-WCAG
luminance; это не APCA и не полное определение LPC. Поправка
Гельмгольца–Кольрауша в APCA **не применяется**.
Comment thread
lemone112 marked this conversation as resolved.

- Исходный код Myndex/apca-w3 (`src/apca-w3.js`): вход — `sRGBtoY` (*«linearize r,
g, b then apply coefficients and sum, return luminance»*, коэффициенты
Expand Down
Loading
Loading