COMPO · 2.16 · LINE COLOR / TONE / OPACITY

Цвет, тон и
прозрачность
линии.

Модуль описывает визуальную заметность линии через цвет, светлоту, насыщенность, alpha и реальный фон под линией. Он отделяет измеримый rendered contrast от семантической важности и от геометрического visual weight.

API-контракт

00 / ownership

CONSUMES

Line Line.role Line.subrole? Line.strokeColor Line.opacity Line.blendMode? Line.pathGeometry Line.strokeWidthPx? RenderedBackgroundSampler Canvas.colorProfile? StylePreset.lineColor? OPTIONAL: Compo2.06.contextualWeight Compo2.15.graphicKind ProtectedContrastTargets[]

PROVIDES

LineColorDescriptor LineContrastDescriptor LineAppearanceProfile LineColorCandidate[] ColorCorrectionRequest[] line.color.localContrast line.color.contrastUniformity line.color.roleColorFit line.color.opacityFit line.color.localColorScore line.color.confidence

DOES NOT OWN

line.visualWeight // 2.06 line.roleLengthFit // 2.07 line.flow.localCue // 2.08 line.graphic.functionFit // 2.15 line.rhythm.* // 2.17 global.colorHierarchy global.paletteHarmony global.balance global.eyeFlow
Ключевой принцип: исходный HEX/RGB — не достаточная характеристика линии. Одна и та же линия после alpha compositing может иметь совершенно разный rendered tone и contrast на разных участках изображения.

Цветовые представления

01 / measurement spaces

sRGB

Input/rendering interchange. Нельзя напрямую использовать sRGB-каналы для физически корректного luminance.

Linear RGB

Нужен для alpha compositing и relative luminance.

OKLab / OKLCH

Удобен для perceptual lightness, chroma и hue-distance в style matching.

Relative luminance

Скалярная тональная величина для contrast calculations.

sRGB channel c ∈ [0,1] linearize(c): if c <= 0.04045: c_lin = c / 12.92 else: c_lin = ((c + 0.055) / 1.055)^2.4 relative luminance: Y = 0.2126 R_lin + 0.7152 G_lin + 0.0722 B_lin // coefficients are STANDARD, not heuristic.

Прозрачность и compositing

02 / effective rendered color

Normal alpha blend

Given: foreground linear RGB F background linear RGB B alpha a ∈ [0,1] C_eff = a * F + (1-a) * B

DERIVED Сначала compositing в linear-light модели, затем при необходимости перевод обратно.

Opacity semantics

opacity = 0: invisible opacity = 1: fully opaque effectiveContrast must be measured AFTER compositing against actual local background

Opacity не оценивается изолированно как «хорошая/плохая».

Blend modes: multiply/screen/overlay и другие режимы требуют соответствующей compositing function. Если движок её не поддерживает точно, контраст помечается approximate и confidence снижается.

Контраст линии

03 / rendered separation from background

Luminance ratio

Y_hi = max(Y_line,Y_bg) Y_lo = min(Y_line,Y_bg) contrastRatio = (Y_hi + 0.05) / (Y_lo + 0.05)

STANDARD Полезный descriptor, но не универсальный эстетический score.

ΔL

toneDifference = abs( L_oklab(line) - L_oklab(bg) )

Perceptual lightness difference удобно использовать отдельно от hue/chroma.

Color distance

colorDistance = distanceOKLab( C_line, C_bg )

Хроматическое различие может поддерживать заметность даже при близкой luminance.

Не смешивать: luminance contrast, perceptual color distance и opacity — независимые descriptors. Composite local contrast должен явно указывать, какие каналы использованы.

Фон вдоль линии

04 / arc-length sampling

Sampling

for q_i uniformly spaced by ARC LENGTH: P_i = sampleAtArcFraction(q_i) background_i = sampleRenderedBackground( P_i, neighborhoodRadius ) lineEffective_i = composite( strokeColor, opacity, background_i )

Используем тот же arc-length contract, что и другие path-модули.

Profile

contrast_i = contrast(lineEffective_i, background_i) profile: mean median min max std p10 p90

Минимум нужен для выявления локальных провалов читаемости.

contrastUniformity = 1 - clamp( robustSpread(contrast_i) / contrastSpreadTau, 0, 1 ) backgroundComplexity: descriptor from sampler/context NOT owned by 2.16 sample count: geometry / implementation policy should scale with pathLengthPx and background spatial frequency.
Sample invariance: среднее не должно зависеть от того, как geometry разбита на segments. Samples распределяются по arc length, а не «по одному на сегмент».

Цвет относительно роли

05 / roleColorFit
Role / contextЧто может требовать presetЧто нельзя считать универсальным правилом
dividerнизкий/средний контраст, если divider должен быть вторичным«divider всегда серый»
connectorдостаточная различимость пути и endpoints«connector всегда ярче объектов»
accentвысокая цветовая/тональная заметность«accent обязательно самый насыщенный»
annotationчитаемость + соответствие technical palette«аннотация должна быть чёрной»
textureограниченный contrast envelope«texture должна быть едва видимой»
frameконтраст относительно содержимого и background«frame должен доминировать»
RoleColorTarget { role, subrole?, contrastRange?, lightnessRange?, chromaRange?, hueFamilies?, opacityRange?, backgroundDependent?, provenance: STYLE_PRESET | EMPIRICAL | HEURISTIC } roleColorFit = fitMeasuredAppearanceToTarget(...)

Contrast floors и ceilings

06 / function-aware thresholds

Floor

if role requires visibility: localContrast should not fall below required minimum minimum comes from: semantic policy accessibility policy StylePreset empirical calibration

Ceiling

if role is subordinate: excessive contrast may exceed target envelope this is ROLE FIT, not generic contrast penalty

N/A

if no target profile exists: roleColorFit = N/A DO NOT invent a universal preferred contrast.

Local color scoring

07 / ownership

OWNS

line.color.localContrast line.color.contrastUniformity line.color.roleColorFit line.color.opacityFit line.color.localColorScore line.color.confidence descriptors: effectiveColorProfile luminanceProfile lightnessProfile colorDistanceProfile contrastRatioProfile

DOES NOT OWN

line.visualWeight global.colorHierarchy global.paletteHarmony global.accentDistribution global.balance global.clutter global.eyeFlow text readability image semantic contrast
localColorScore = weightedMeanApplicable([ roleColorFit, opacityFit, requiredVisibilityFit?, contrastUniformity? ]) IMPORTANT: raw contrast is usually a DESCRIPTOR, not automatically a positive vote. More contrast ≠ always better.
Metric dependency: если localColorScore уже содержит roleColorFit/opacityFit, Compo 2.20 должен голосовать composite-first и не суммировать дочерние метрики второй раз.

Correction requests

08 / bounded changes

CHANGE COLOR

Сдвинуть L/C/H в разрешённом preset диапазоне.

CHANGE OPACITY

Изменить alpha, сохраняя role/style constraints.

LOCAL VARIANT

Если фон неоднороден — предложить сегментный/адаптивный color treatment, если стиль допускает.

REQUEST BACKDROP

Запросить подложку/halo у внешнего owner, а не менять геометрию линии самовольно.

ColorCorrectionRequest { lineId, reasonCode: BELOW_VISIBILITY_FLOOR | ABOVE_ROLE_CONTRAST_CEILING | LOW_CONTRAST_UNIFORMITY | OPACITY_OUT_OF_TARGET | HUE_OUT_OF_STYLE | UNSUPPORTED_BLEND_MODE, targetOwner: "LINE_STYLE" | "STYLE_PRESET" | "GLOBAL_SOLVER" | "BACKGROUND_OWNER", requestedChange, allowedRange?, confidence }

Визуальная лаборатория

09 / appearance cases
Одинаковая геометрия — разные tone/contrast profiles. 2.16 измеряет эту разницу; 2.06 не должен пересчитывать её как геометрический stroke weight.
Одна линия проходит по двум фонам: effective appearance и contrast должны вычисляться локально вдоль path.
Один и тот же hue/chroma, но разный alpha → разный effective luminance и contrast.
Неровный background создаёт contrast profile. Среднее значение без min/p10 может скрыть локальное исчезновение линии.
Три линии могут иметь одинаковый luminance contrast, но разную hue-role семантику. Это учитывается через StylePreset/roleColorFit.

Детерминированные тесты

10 / regression

TEST 01 · black/white

line=#000 bg=#fff opacity=1 expected: contrastRatio=21

TEST 02 · transparent black

line=#000 bg=#fff alpha=0.5 expected: effective color computed after linear compositing

TEST 03 · opacity zero

alpha=0 expected: effectiveColor=background contrastRatio=1

TEST 04 · local background

same stroke two backgrounds expected: two different local contrasts

TEST 05 · no universal max

accent target: high contrast texture target: low contrast expected: roleColorFit differs for same measured contrast

TEST 06 · N/A

no role target expected: roleColorFit=N/A MUST NOT default to 1 or 0

TEST 07 · sampling

same path geometry different segmentization expected: same arc-length contrast profile within tolerance

TEST 08 · ownership

change line width without color/bg change expected: color descriptors stable except neighborhood effects explicitly configured

TEST 09 · global leak

global hierarchy changes expected: 2.16 owned metrics unchanged unless role target input changes

LIVE REFERENCE TESTS

IDInputComputedStatus

DATA FOR LAYOUT ENGINE

11 / machine contract
{ "module": "Compo 2.16", "name": "line_color_tone_opacity", "version": "1.0", "scope": "LOCAL_LINE_COLOR_APPEARANCE", "consumes": [ "Compo2.00.Line", "Compo2.00.Line.role", "Compo2.00.Line.subrole?", "Compo2.00.LinePathGeometry", "Line.strokeColor", "Line.opacity", "Line.blendMode?", "RenderedBackgroundSampler", "Canvas.colorProfile?", "Compo2.06.contextualWeight?", "Compo2.15.graphicKind?", "StylePreset.lineColor?" ], "provides": [ "LineColorDescriptor", "LineContrastDescriptor", "LineAppearanceProfile", "LineColorCandidate[]", "ColorCorrectionRequest[]" ], "owns_metrics": [ "line.color.localContrast", "line.color.contrastUniformity", "line.color.roleColorFit", "line.color.opacityFit", "line.color.localColorScore", "line.color.confidence" ], "descriptors_only": [ "effectiveColorProfile", "luminanceProfile", "lightnessProfile", "colorDistanceProfile", "contrastRatioProfile", "contrastMin", "contrastMedian", "contrastP10", "contrastP90" ], "metric_policy": { "primary_vote": "line.color.localColorScore", "raw_contrast": "DESCRIPTOR_NOT_MONOTONIC_BEAUTY_SCORE", "components": "diagnostic_only_by_default", "role_targets": "STYLE_OR_SEMANTIC_DEPENDENT", "N/A": "EXCLUDE_FROM_AGGREGATION" }, "standard_math": { "srgb_linear_threshold": 0.04045, "srgb_low_divisor": 12.92, "srgb_offset": 0.055, "srgb_scale": 1.055, "srgb_gamma": 2.4, "relative_luminance_coefficients": [0.2126, 0.7152, 0.0722], "contrast_luminance_offset": 0.05 }, "initial_heuristics": { "contrast_spread_tau": { "value": 2.0, "source": "INITIAL_HEURISTIC" }, "background_sample_radius_px": { "value": 3, "source": "IMPLEMENTATION_HEURISTIC" }, "minimum_samples": { "value": 16, "source": "IMPLEMENTATION_HEURISTIC" } }, "hard_rules": [ "sRGB_channels_must_be_linearized_before_relative_luminance_and_linear_alpha_compositing", "effective_contrast_must_be_measured_after_compositing_against_actual_local_background", "arc_length_sampling_must_not_depend_on_segment_count", "raw_contrast_must_not_be_treated_as_monotonically_better", "roleColorFit_must_be_NA_when_no_target_exists", "color_module_must_not_own_global_palette_harmony_or_color_hierarchy", "line_visual_weight_remains_owned_by_2.06", "rhythm_remains_owned_by_2.17", "unsupported_blend_modes_must_reduce_confidence_or_defer" ], "deferred_inputs": [ "global.colorHierarchy", "global.paletteHarmony", "global.accentDistribution", "global.balanceScore", "global.eyeFlowScore", "Compo2.17.rhythmScore" ], "recompute_when": [ "strokeColor changes", "opacity changes", "blendMode changes", "background under path changes", "LinePathGeometry changes", "role/subrole changes", "StylePreset.lineColor changes", "color profile changes" ], "finality": "LOCAL_COLOR_APPEARANCE_ONLY" }
ARCHITECTURE CHECKPOINT: 2.16 владеет rendered appearance линии: effective color, tone, opacity, local contrast и соответствием цветовой роли. Он не превращает контраст в универсальный «чем больше, тем лучше» score и не забирает global palette/hierarchy. Следующий Compo 2.17 — ритм линий и stroke-pattern.
VISITS / ...