
Methodology
Color mixing, opacity, and gradient interpolation
Mixing and interpolation tools are deliberately explicit about their model because a 50 percent mix can look different in sRGB, HSL, Lab, OKLab, or alpha compositing.
Short answer
Hue Codex exposes the interpolation model, mixes channels or model components deterministically, composites opacity with source-over math, and treats gradient text contrast as sampled guidance rather than full-layout proof.
- sRGB, HSL, Lab, and OKLab mixes follow different paths and can produce visibly different midpoints.
- sRGB mode mixes encoded 8-bit sRGB channel values, not linear-light sRGB values.
- Opacity tools composite a foreground over a background before contrast and export values are calculated.
- Gradient tools keep stop positions, midpoint hints, interpolation space, and fallback CSS explicit.
- Overlay text on a gradient must be checked at the final placement, not only against sampled stops.
Standards status
These badges identify which parts of this methodology are standards-backed, draft-track, source-origin references, Hue Codex-specific, approximate, or dependent on browser behavior.
Formulas, choices, heuristics, and limits
This separates standards-based formulas from Hue Codex implementation decisions, product heuristics, and known limitations for this methodology.
Standards-based formulas
Formula, threshold, syntax, or data behavior taken from a cited standard. Primary and model sources can inform implementation, but they are not automatically standards.
- Alpha compositing uses source-over channel math before contrast checks.
- CSS Color 4 color spaces provide the stable conversion context for sRGB, Lab, and OKLab mixing modes.
Implementation choices
How Hue Codex chooses to parse, normalize, round, export, or sequence calculations.
- Hue Codex exposes the chosen interpolation model instead of hiding mix-space differences.
- Color Mixer weight is the percentage of Color A. A 0 percent A mix resolves to Color B, a 100 percent A mix resolves to Color A, and intermediate values use A * weightA + B * (1 - weightA).
- The Color Mixer sRGB mode interpolates encoded 8-bit sRGB channel values directly; it does not linearize channels before mixing.
- Opacity Calculator composites resolved HEX colors in encoded 8-bit sRGB channel space; it does not linearize channels before source-over alpha math.
- Color Mixer retains endpoint alpha, interpolates alpha linearly, and uses alpha-premultiplied weighting for sRGB, HSL, Lab, and OKLab component interpolation before serializing the result.
- Color Mixer CSS color-mix() exports preserve the concrete authored endpoint values, select one supported interpolation space, and use explicit complementary percentages that always sum to 100 percent.
- HSL mode converts both inputs to RGB-derived HSL, uses the shorter signed hue path with a -180 degree tie, linearly interpolates saturation and lightness, then converts back to sRGB.
- Gradient tools record stops, midpoint hints, direction, repeating state, and fallback CSS. The sRGB fallback gradient preserves the same HEX stops and positions, then removes the modern interpolation clause.
- Gradient palette extraction samples normalized stop positions according to the selected mode.
Hue Codex heuristics
Product rankings, bands, labels, suggestions, or role hints that are useful guidance but not external standards.
- Gradient text guidance, extracted role hints, and review notes are sampled Hue Codex guidance.
- The selected fallback pattern and support caveat text are product handoff choices.
Known limitations
Caveats, edge cases, browser dependencies, approximations, or contexts the method does not prove.
- CSS Color 5 color-mix() and related modern interpolation features are draft-standard context.
- Hue Codex does not parse an arbitrary color-mix() expression as input or generate forms with omitted percentages, more than two colors, currentColor, contextual colors, or leftover percentage alpha.
- Internal component interpolation uses each endpoint’s resolved sRGB fallback channels, so an authored out-of-sRGB endpoint may differ from a color-managed browser interpolation of its original wide-gamut syntax.
- Sampled gradient contrast does not test every pixel or every final text position.
- Encoded sRGB mixes differ from linear-light sRGB mixes; Hue Codex labels the mode so users can choose the interpolation model intentionally.
- Encoded sRGB opacity compositing can differ from linear-light or color-managed rendering pipelines; Hue Codex reports the resolved fallback it calculates rather than claiming to reproduce every renderer.
- Achromatic HSL inputs have powerless hue; Hue Codex currently carries the derived numeric fallback hue 0 through HSL interpolation rather than preserving an authored missing-hue marker.
- Browser support and rendering differ across gradient syntax, interpolation spaces, and wide-gamut output.
Representative automated fixture coverage
Hue Codex imports versioned JSON fixtures and tests the corresponding pure production functions directly. The groups below are the representative regression evidence most relevant to this methodology.
- Parsing fixtures: Accepted CSS color syntax, alpha behavior, clamping, invalid input, and out-of-gamut fallback cases. View the JSON fixtures.
- Conversion fixtures: sRGB, XYZ, Lab/LCH, OKLab/OKLCH conversion values, round trips, and precision boundaries. View the JSON fixtures.
- Export fixtures: Displayed-versus-exported values, parseable CSS and JSON, and safe allowlisted SVG output. View the JSON fixtures.
What a pass means. A passing group confirms the named vectors and tested properties still match the documented implementation. It does not prove every possible input, final browser rendering, full accessibility conformance, profile-aware print output, or every downstream export consumer.
Mixing models
The Color Mixer offers sRGB channel mixing, HSL shorter-hue mixing, Lab component mixing, and OKLab component mixing. The selected mode determines the midpoint and the exported CSS color-mix space where applicable.
HSL mixing interpolates hue along the shorter path and mixes saturation and lightness. Lab and OKLab mixing interpolate Cartesian components. sRGB mixing interpolates encoded channel values for predictable browser-oriented handoff.
Red/blue 50% midpoint visual test cases
These swatches use the same endpoints and weight as the numeric vectors later on this page: Color A #FF0000, Color B #0000FF, weightA = 0.5, weightB = 0.5. The visual difference is intentional because each interpolation model follows a different path through color space.
The linear-light sRGB swatch is shown as a comparison case only. Hue Codex sRGB mode uses encoded 8-bit sRGB channel mixing, so its expected midpoint remains #800080.
50% red/blue mix by space
Use this row as a quick visual regression check for the 50 percent red/blue mix examples.
Endpoints
Hue Codex midpoint outputs
Comparison case
sRGB mixing domain
Hue Codex sRGB mode interpolates encoded sRGB channel values after HEX input has been resolved to 8-bit RGB channels. It does not convert channels to linear-light sRGB before mixing, and it does not convert a linear-light result back to encoded sRGB.
For each channel, the formula is channel = round(clamp(A8 * weightA + B8 * (1 - weightA), 0, 255)). This makes 50 percent Color A #FF0000 and Color B #0000FF resolve to #800080. A linear-light sRGB mix of those same endpoints would be much lighter, approximately #BC00BC, so the domain choice is intentionally visible.
| Mode/domain | Formula domain | 50% #FF0000 / #0000FF result |
|---|---|---|
| Hue Codex sRGB mode | Encoded 8-bit sRGB channels | #800080 |
| Linear-light sRGB comparison | Linearized sRGB channels converted back to encoded sRGB | Approximately #BC00BC; not used by Hue Codex sRGB mode |
HSL hue interpolation
HSL mode first converts each resolved sRGB input to RGB-derived HSL. Hue angles are normalized into [0, 360), saturation and lightness are percentages, and the result is converted back through HSL-to-RGB with final channel rounding and clamping.
Hue travels the shorter signed path from Color A to Color B as Color A weight decreases. The signed delta is ((hueB - hueA + 540) % 360) - 180, so the range is [-180, 180). A perfect 180-degree tie resolves to -180, meaning it travels in the decreasing-hue direction from Color A.
Achromatic inputs have powerless hue because saturation is zero. Hue Codex currently uses the derived RGB-to-HSL fallback hue 0 for those colors during HSL mixing, while saturation and lightness still interpolate normally.
| Area | Rule |
|---|---|
| Hue source | hueA and hueB come from rgb_to_hsl(hex_to_rgb(input)); achromatic colors return h = 0 and s = 0 |
| Hue delta | delta = ((hueB - hueA + 540) % 360) - 180 |
| Hue result | hue = normalize_degrees(hueA + delta * (1 - weightA)) |
| 180deg tie | Because delta is in [-180, 180), exactly opposite hues use delta = -180 |
| Hue wrap | normalize_degrees returns a value in [0, 360), so paths can cross 0deg cleanly |
| Saturation | s = saturationA * weightA + saturationB * (1 - weightA) |
| Lightness | l = lightnessA * weightA + lightnessB * (1 - weightA) |
| Powerless hue | If one or both inputs are grayscale, their derived h = 0 participates in hue math even though that hue is non-authoritative |
Alpha-premultiplied color mixing
Color Mixer accepts concrete translucent endpoints. It interpolates endpoint alpha linearly, converts the visible Color A weight into an alpha-premultiplied component weight, interpolates the selected sRGB, HSL, Lab, or OKLab components with that weight, and attaches the mixed alpha to the result.
For opaque endpoints, the adjusted weight equals the visible slider weight. If one endpoint is more transparent, it contributes proportionally less to the mixed components while its alpha still contributes to the resulting opacity.
The internal mixer uses resolved sRGB fallback channels as the starting coordinates. The CSS color-mix() handoff preserves the authored endpoint syntax so a capable color-managed browser may retain more wide-gamut information than the internal fallback preview.
| Context | Alpha behavior | Premultiplication? |
|---|---|---|
| Hue Codex internal sRGB/HSL/Lab/OKLab mixer | Endpoint alpha is preserved and interpolated linearly | Yes. Component weight is adjusted by alpha before interpolation |
| Hue Codex Color Mixer CSS export | Preserves concrete authored endpoints with weights that sum to 100% | The browser applies the selected CSS interpolation space and alpha rules |
| Arbitrary CSS Lab/OKLab interpolation with alpha | Browser applies CSS color interpolation rules for non-opaque colors | Yes. Rectangular components are premultiplied by alpha before interpolation |
| Opacity Calculator | Composites a resolved foreground over a resolved background after color choice | Separate source-over compositing step, not Lab/OKLab interpolation |
Weight orientation
Color Mixer weight is the influence of Color A, the first input. The normalized value is weightA = clamp(weightPercent, 0, 100) / 100, and weightB = 1 - weightA.
For numeric channels or Cartesian components, Hue Codex uses result = A * weightA + B * (1 - weightA). Therefore 0 percent A returns Color B, 50 percent A returns an equal A/B mix, and 100 percent A returns Color A. Exported CSS follows the same orientation: color-mix(in space, A weightPercent%, B (100 - weightPercent)%).
| UI weight | Formula weights | Expected endpoint |
|---|---|---|
| 0% A | A * 0 + B * 1 | Color B, the second input |
| 50% A | A * 0.5 + B * 0.5 | Equal mix |
| 100% A | A * 1 + B * 0 | Color A, the first input |
CSS color-mix handoff
CSS Color 5 defines color-mix() with percentage normalization. CSS can treat omitted or over-specified percentages as normalized mix weights, and if the specified percentages sum to less than 100 percent the resulting color can carry reduced alpha.
Hue Codex Color Mixer avoids those ambiguous percentage cases. It accepts two concrete colors, calculates an alpha-aware resolved sRGB fallback internally, and emits CSS in the form color-mix(in space, A weightA%, B weightB%) where weightA + weightB is always exactly 100 percent.
The visible Color Mixer weight control is an integer-percentage slider. CSS export uses the same clamped control value that drives the internal mix; it does not apply a separate rounding step that could make exported color-mix() drift from the calculated result.
The exported CSS is therefore a reproducible handoff for the selected two-color mix, not a parser or simulator for every valid color-mix() expression. Hue Codex does not generate leftover-percentage-alpha output, omitted-percentage output, more-than-two-color color-mix(), currentColor, system colors, or other contextual source values.
| Hue Codex mode | Internal calculation | CSS handoff space | Percentage behavior |
|---|---|---|---|
| Default sRGB | Encoded 8-bit sRGB channel interpolation | srgb | A weight is explicit; B weight is 100 - A |
| HSL shorter hue | RGB-derived HSL with shorter-hue interpolation | hsl shorter hue | A weight is explicit; B weight is 100 - A |
| Lab | Lab component interpolation | lab | A weight is explicit; B weight is 100 - A |
| OKLab | OKLab component interpolation | oklab | A weight is explicit; B weight is 100 - A |
Core formulas
These compact formulas are the reproducible core for Hue Codex encoded sRGB mixing and opacity compositing. In the mixing formulas, w is the normalized Color A weight in [0, 1]. In the opacity formula, alpha is the normalized foreground opacity in [0, 1].
Both the sRGB mix formula and the opacity formula below operate on encoded 8-bit sRGB channel values after HEX parsing. Hue Codex does not convert those channels to linear-light RGB before mixing or compositing.
Given weight w in [0, 1]:
sRGB encoded mix:
R = round(Ra * w + Rb * (1 - w))
G = round(Ga * w + Gb * (1 - w))
B = round(Ba * w + Bb * (1 - w))
Source-over opacity in encoded sRGB channel space:
effective = foreground * alpha + background * (1 - alpha)
Per-channel opacity:
Re = round(Rf * alpha + Rbg * (1 - alpha))
Ge = round(Gf * alpha + Gbg * (1 - alpha))
Be = round(Bf * alpha + Bbg * (1 - alpha))
No srgb_to_linear() or linear_to_srgb() transfer step is applied.
Opacity compositing
Opacity Calculator resolves a transparent foreground over a chosen background with source-over channel math: effective channel equals foreground channel times alpha plus background channel times one minus alpha.
The channel space is encoded 8-bit sRGB from the resolved HEX values. Hue Codex does not linearize to linear-light RGB for opacity compositing, and it rounds the resulting encoded channels back to HEX.
This means #000000 at 50 percent alpha over #FFFFFF resolves to #808080 in Hue Codex. A linear-light compositing comparison would be much lighter, approximately #BCBCBC, so the channel-space choice is intentionally documented as a Hue Codex implementation choice.
Contrast checks run against the effective composited color, not the original transparent declaration. Suggestion helpers can search for a source color whose composited result reaches the selected contrast target.
Gradient generation and sampling
Gradient Generator accepts concrete CSS color literals, including alpha HEX, fixed named colors, RGB, HSL, HWB, Lab/LCH, OKLab/OKLCH, and supported color() profiles. It records those authored source colors with stop positions, optional midpoint hints, direction, shape, repeating state, and interpolation syntax, then creates a modern gradient plus a broader sRGB fallback.
Gradient Generator overlay text guidance samples the source stop colors plus each adjacent midpoint hint. Each sample is composited over the tool's declared white review canvas before contrast is scored. Midpoint alpha uses source-over-compatible premultiplied-alpha interpolation in encoded sRGB, so this remains a contrast heuristic rather than a full rendered-pixel proof for every interpolation mode and backdrop.
Gradient Palette Extractor samples along normalized stop positions and can interpolate in sRGB, HSL, Lab, or OKLab depending on the selected mode. It labels extracted swatches by their source position and provides contrast and role hints.
| Tool/output | Sample positions | How colors are resolved |
|---|---|---|
| Gradient Generator overlay text | Every source stop plus every midpoint hint. Default 3-color setup: stops 0%, 52%, 100%; midpoint hints 26%, 76%. For other color counts, default stops are evenly spaced and hints are halfway between adjacent stops. | Authored stop syntax and alpha remain in the gradient. Review samples resolve to sRGB, use premultiplied-alpha encoded-sRGB midpoint mixing, then composite over white for contrast. |
| Gradient Palette Extractor, 5 swatches | 0%, 25%, 50%, 75%, 100% | Each position is resolved through the selected interpolation mode and current stop/hint state. |
| Gradient Palette Extractor, 7 swatches | 0%, 16.6667%, 33.3333%, 50%, 66.6667%, 83.3333%, 100% | Each position is resolved through the selected interpolation mode and current stop/hint state. Display labels round to whole percentages. |
| Gradient Palette Extractor, 9 swatches | 0%, 12.5%, 25%, 37.5%, 50%, 62.5%, 75%, 87.5%, 100% | Each position is resolved through the selected interpolation mode and current stop/hint state. |
Gradient fallback generation
When the selected gradient interpolation is modern syntax, Hue Codex creates a fallback by generating the same gradient with the interpolation override set to sRGB. In CSS output this means the modern "in oklab", "in display-p3", "in hsl shorter hue", or similar interpolation clause is removed for the fallback declaration.
Stop colors are not re-sampled through the modern interpolation path. The modern declaration retains each validated authored color literal. For the fallback declaration, every stop is converted to its resolved sRGB channels while preserving alpha as exact slash notation when needed. Stop positions, midpoint hints, type, repeating flag, linear direction, radial shape/size/position, and conic angle/position are preserved.
The fallback block is layered for the CSS cascade: first a solid background-color, then the sRGB fallback gradient, then the modern gradient only when it differs from the fallback. Browsers that cannot parse the modern interpolation declaration can keep the earlier sRGB gradient declaration.
The support caveat shown by the tool is: "Default gradient syntax is the broadest CSS option for production." for sRGB gradients, and "{Color space label} is modern gradient syntax; keep the sRGB gradient fallback for broader support." for non-sRGB interpolation modes.
| Item | Fallback rule |
|---|---|
| Stop colors | Keep validated authored color literals in the modern declaration; use resolved sRGB channels with preserved alpha in the broader fallback |
| Stop positions | Preserve every stop percentage exactly as normalized by the stop controls |
| Midpoint hints | Preserve hint percentages exactly; hints remain CSS color-hint positions between stops |
| Interpolation clause | Fallback uses interpolation override srgb, which emits no "in ..." interpolation text |
| Solid fallback | Use the encoded-sRGB color sampled at 50% as background-color before any gradient image |
| Cascade order | background-color first, fallback background-image second, modern background-image third when different |
| sRGB-selected mode | Modern and fallback CSS are identical, so only one background-image declaration is emitted |
| Non-sRGB-selected mode | Readiness displays "{label} + sRGB fallback" with a warning status |
Limits
- Sampled gradient contrast does not test every pixel in the final component.
- Gradient-list share state preserves supported concrete CSS source strings and exact parsed decimal alpha. Older links that already contain eight-digit HEX retain that exact 8-bit alpha value when reopened.
- CSS interpolation support varies by browser and syntax; fallbacks are included for broader compatibility.
- Different mix spaces can be equally valid for different goals, so Hue Codex reports mode choice rather than hiding it.
- Composited values depend on the selected background; changing the backdrop changes the final color and contrast.
Validation checks
| Check | Expected behavior |
|---|---|
| 0 percent A mix endpoint | For Color A #FF0000 and Color B #0000FF, output is #0000FF |
| 100 percent A mix endpoint | For Color A #FF0000 and Color B #0000FF, output is #FF0000 |
| Color Mixer CSS export at 30 percent A | For Color A #FF0000 and Color B #0000FF in sRGB mode, CSS handoff is color-mix(in srgb, #FF0000 30%, #0000FF 70%) |
| Color Mixer avoids leftover-alpha color-mix output | Hue Codex does not emit color-mix(in srgb, #FF0000 30%, #0000FF 30%); B is exported as 70%, not 30%, so exported percentages sum to 100% and no color-mix alpha is introduced |
| Color Mixer does not rely on over-100 normalization | Hue Codex does not emit color-mix(in srgb, #FF0000 80%, #0000FF 80%); exported weights are explicit complements from the slider |
| 50 percent A sRGB mix | For Color A #FF0000 and Color B #0000FF, encoded channels average to #800080 |
| 50 percent A linear-light sRGB comparison | The same endpoints would be approximately #BC00BC in linear-light sRGB; Hue Codex sRGB mode does not use that path |
| 50 percent A HSL shorter-hue mix | For Color A #FF0000 and Color B #0000FF, hue travels through 300deg and output is #FF00FF |
| HSL 180-degree tie from red to cyan | For Color A #FF0000 and Color B #00FFFF at 50 percent A, tie delta is -180 and output is #8000FF |
| HSL 180-degree tie from cyan to red | For Color A #00FFFF and Color B #FF0000 at 50 percent A, tie delta is -180 from cyan and output is #80FF00 |
| HSL hue wrap | For hue 350deg to hue 10deg at 50 percent A, normalized midpoint hue is 0deg |
| HSL grayscale fallback hue | For Color A #808080 and Color B #FF0000 at 50 percent A, grayscale h=0 and s=0 produce #BF4040 |
| Alpha 100 percent | Effective color equals the foreground |
| Alpha 0 percent | Effective color equals the background |
| Gradient stop ordering | Smart spacing keeps stop positions ordered |
| Gradient Generator 3-color overlay samples | Default source stops are 0%, 52%, 100% and overlay guidance also checks midpoint hints at 26% and 76% |
| Gradient Generator 5-color overlay samples | Default source stops are 0%, 25%, 50%, 75%, 100% and overlay guidance also checks hints at 12.5%, 37.5%, 62.5%, 87.5% |
| Gradient fallback for OKLab interpolation | Modern CSS keeps "in oklab"; fallback CSS removes the interpolation clause and preserves the same HEX stops, stop percentages, hints, direction, and gradient type |
| Gradient fallback cascade order | Fallback export emits background-color, then fallback background-image, then modern background-image when the modern CSS differs from the fallback |
| Gradient fallback for sRGB interpolation | Modern CSS and fallback CSS are identical, so only one background-image declaration is emitted after background-color |
| Gradient support caveat for modern interpolation | Tool copy says "{Color space label} is modern gradient syntax; keep the sRGB gradient fallback for broader support." |
| Gradient support caveat for sRGB interpolation | Tool copy says "Default gradient syntax is the broadest CSS option for production." |
| Gradient Palette Extractor 5 swatches | Sample positions are 0%, 25%, 50%, 75%, 100% |
| Gradient Palette Extractor 7 swatches | Sample positions are 0%, 16.6667%, 33.3333%, 50%, 66.6667%, 83.3333%, 100%; visible labels round to 0%, 17%, 33%, 50%, 67%, 83%, 100% |
| Gradient Palette Extractor 9 swatches | Sample positions are 0%, 12.5%, 25%, 37.5%, 50%, 62.5%, 75%, 87.5%, 100% |
Mixing and gradient pseudocode
Hue Codex treats the selected interpolation space as part of the result. The pseudocode below makes percentage orientation, hue handling, stop hints, and sample positions explicit.
weight_a_from_percent(weight_percent):
# The UI weight is Color A's percentage.
# 0 percent A returns Color B; 100 percent A returns Color A.
return clamp(weight_percent, 0, 100) / 100
mix_number(a, b, weight_a):
# weight_a is the fraction of Color A.
# weight_b = 1 - weight_a is the fraction of Color B.
# Formula: result = (a * weight_a) + (b * (1 - weight_a)).
return a * weight_a + b * (1 - weight_a)
shortest_hue_mix(hue_a, hue_b, weight_a):
# Hue values are normalized to [0, 360).
# delta is in [-180, 180). A 180 degree tie becomes -180.
delta = ((hue_b - hue_a + 540) % 360) - 180
return normalize_degrees(hue_a + delta * (1 - weight_a))
mix_hex(a, b, weight_a, mode):
if mode == rgb:
rgb_a = hex_to_encoded_srgb_8bit(a)
rgb_b = hex_to_encoded_srgb_8bit(b)
# This is encoded sRGB channel interpolation, not linear-light sRGB.
# No srgb_to_linear() or linear_to_srgb() transfer step is applied.
return rgb_to_hex(
r = mix_number(rgb_a.r, rgb_b.r, weight_a),
g = mix_number(rgb_a.g, rgb_b.g, weight_a),
b = mix_number(rgb_a.b, rgb_b.b, weight_a)
)
if mode == hsl:
hsl_a = rgb_to_hsl(hex_to_rgb(a))
hsl_b = rgb_to_hsl(hex_to_rgb(b))
# Achromatic RGB values have s = 0 and derived hue h = 0.
# Hue is powerless in those cases, but this mixer uses the derived 0 value.
h = shortest_hue_mix(hsl_a.h, hsl_b.h, weight_a)
s = mix_number(hsl_a.s, hsl_b.s, weight_a)
l = mix_number(hsl_a.l, hsl_b.l, weight_a)
return rgb_to_hex(hsl_to_rgb(h, s, l))
if mode == lab:
# weight_a has already been adjusted for endpoint alpha.
return rgb_to_hex(lab_to_rgb(mix L, a, b components))
if mode == oklab:
# weight_a has already been adjusted for endpoint alpha.
return rgb_to_hex(oklab_to_rgb(mix L, a, b components))
premultiplied_mix_weight(color_a, color_b, weight_a):
alpha = alpha_a * weight_a + alpha_b * (1 - weight_a)
component_weight_a =
if alpha > 0 then (alpha_a * weight_a) / alpha
else weight_a
return { alpha, component_weight_a }
alpha_aware_internal_mix(color_a, color_b, weight_a, mode):
weights = premultiplied_mix_weight(color_a, color_b, weight_a)
base_hex = mix_hex(
opaque_srgb_channels(color_a),
opaque_srgb_channels(color_b),
weights.component_weight_a,
mode
)
return attach_alpha(base_hex, weights.alpha)
composite_hex_encoded_srgb(foreground_hex, background_hex, alpha):
# Hue Codex opacity tools use encoded 8-bit sRGB channels from HEX.
# They do not linearize to linear-light RGB before compositing.
fg = hex_to_encoded_srgb_8bit(foreground_hex)
bg = hex_to_encoded_srgb_8bit(background_hex)
a = clamp(alpha, 0, 1)
return rgb_to_hex(
r = round(fg.r * a + bg.r * (1 - a)),
g = round(fg.g * a + bg.g * (1 - a)),
b = round(fg.b * a + bg.b * (1 - a))
)
linear_light_comparison_only(foreground_hex, background_hex, alpha):
fg = srgb_to_linear(hex_to_srgb_unit(foreground_hex))
bg = srgb_to_linear(hex_to_srgb_unit(background_hex))
mixed = fg * alpha + bg * (1 - alpha)
return linear_to_srgb(mixed)
css_space_for_mode(mode):
if mode == rgb: return "srgb"
if mode == hsl: return "hsl shorter hue"
if mode == lab: return "lab"
if mode == oklab: return "oklab"
color_mix_export(color_a_source, color_b_source, weight_percent, mode):
# Hue Codex preserves concrete authored endpoint syntax, including alpha,
# and emits explicit complementary weights.
# The visible Color Mixer slider is step=1, so normal UI state is integer percent.
# If restored/imported state supplies a fractional control value, export that
# same clamped value so the CSS handoff matches the internal mix.
weight_a = clamp(weight_percent, 0, 100)
weight_b = 100 - weight_a
space = css_space_for_mode(mode)
return "color-mix(in {space}, {color_a_source} {weight_a}%, {color_b_source} {weight_b}%)"
css_color_mix_normalization_context(percent_a, percent_b):
total = percent_a + percent_b
if total < 100:
# CSS can multiply the mixed color alpha by total / 100.
# Hue Codex Color Mixer does not emit this form.
return "leftover-alpha color-mix form; not generated"
if total > 100:
# CSS can normalize the percentages back to a 100% total.
# Hue Codex does not rely on this branch because output already sums to 100.
return "over-specified color-mix form; not generated"
return "Hue Codex export form"
sample_positions(count):
if count == 1: return [0]
return [index / (count - 1) for index in 0..count-1]
gradient_generator_default_stops(source_color_count):
if source_color_count <= 1:
stop[0] = 0
else if source_color_count == 3:
# Gradient Generator's 3-color fixture uses an intentional center bias.
stop = [0, 52, 100]
else:
stop[index] = index / (source_color_count - 1) * 100
hint[index] = (stop[index] + stop[index + 1]) / 2
gradient_generator_overlay_samples(source_colors, stops, hints):
# Used for suggested overlay text in the Gradient Generator.
# Samples are source stop colors plus one 50/50 preview mix per adjacent pair.
samples = []
for index, hex in source_colors:
samples.push({ position: stops[index], hex: hex })
if index < source_colors.length - 1:
samples.push({
position: hints[index],
hex: mix_hex(hex, source_colors[index + 1], 0.5, rgb)
})
return samples
gradient_palette_extractor_positions(count):
# UI choices are 5, 7, or 9 samples.
# 5 => [0, .25, .5, .75, 1]
# 7 => [0, .1666667, .3333333, .5, .6666667, .8333333, 1]
# 9 => [0, .125, .25, .375, .5, .625, .75, .875, 1]
return sample_positions(count)
gradient_palette_default_stops(source_color_count):
stop[index] = source_color_count <= 1 ? 0 : index / (source_color_count - 1) * 100
hint[index] = (stop[index] + stop[index + 1]) / 2
hinted_progress(raw_progress, local_hint):
h = clamp(local_hint, 0.001, 0.999)
if raw_progress <= h:
return 0.5 * (raw_progress / h)
return 0.5 + 0.5 * ((raw_progress - h) / (1 - h))
sample_gradient(source_colors, position_0_to_1, mode, stops, hints):
p = clamp(position_0_to_1 * 100, 0, 100)
if p <= first_stop: return first_color
if p >= last_stop: return last_color
segment = first index where p <= stop[index + 1]
start = stop[segment]
end = stop[segment + 1]
raw = clamp((p - start) / (end - start), 0, 1)
local_hint = (hint[segment] - start) / (end - start)
progress = hinted_progress(raw, local_hint)
# mix_hex weight is fraction of the segment start color.
return mix_hex(source_colors[segment],
source_colors[segment + 1],
1 - progress,
mode)
gradient_interpolation_method(interpolation, hue_method):
if interpolation is empty or interpolation == srgb:
return ""
if interpolation is polar and hue_method is present:
return " in " + interpolation + " " + hue_method + " hue"
return " in " + interpolation
gradient_css(data, interpolation_override = data.interpolation):
interpolation_text = gradient_interpolation_method(
interpolation_override,
data.hue_method
)
stop_list = join([
color[index] + " " + stop[index] + "%"
plus hint[index] + "%" between adjacent stops
])
if data.type == linear:
head = data.direction_keyword or data.angle + "deg"
if data.type == radial:
head = data.radial_shape + " " + data.radial_size +
" at " + data.position_x + "% " + data.position_y + "%"
if data.type == conic:
head = "from " + data.angle + "deg at " +
data.position_x + "% " + data.position_y + "%"
return gradient_function(data.type, data.repeating) +
"(" + head + interpolation_text + ", " + stop_list + ")"
gradient_fallback_css(data):
# Source colors are already resolved six-digit sRGB HEX values.
# The fallback does not resample the modern gradient path.
# It preserves colors, stop positions, midpoint hints, type, direction,
# radial/conic placement, and repeating state.
data.fallback_css = gradient_css(data, srgb)
data.modern_css = gradient_css(data, data.interpolation)
data.solid_fallback = encoded_srgb_midpoint_at_50_percent(data)
declarations = [
"background-color: " + data.solid_fallback,
"background-image: " + data.fallback_css
]
if data.modern_css != data.fallback_css:
declarations.push("background-image: " + data.modern_css)
return declarations
Reproducible test vectors
These vectors show why Hue Codex names the active mix space. The same endpoints and percentage produce different midpoint colors in different models.
| Input | Expected output | Notes |
|---|---|---|
| #FF0000 and #0000FF, 50/50 sRGB mix | #800080 | Encoded RGB channels are averaged and rounded |
| #FF0000 and #0000FF, 50/50 linear-light comparison | Approximately #BC00BC; not used by Hue Codex sRGB mode | Linear-light mixing would linearize channels before interpolation and convert back to encoded sRGB |
| #FF0000 and #0000FF, 50/50 HSL shorter-hue mix | #FF00FF | Hue travels the shorter path through 300deg |
| #FF0000 and #00FFFF, 50/50 HSL 180deg tie | #8000FF | Tie resolves to delta -180, so hue travels from 0deg to 270deg at midpoint |
| #00FFFF and #FF0000, 50/50 HSL 180deg tie | #80FF00 | Reversing inputs starts at 180deg and travels through 90deg at midpoint |
| HSL hue 350deg to 10deg, 50/50 | 0deg | Hue wrap crosses 360deg/0deg on the shorter path |
| #808080 and #FF0000, 50/50 HSL mix | #BF4040 | Gray resolves to h=0, s=0; saturation and lightness are interpolated |
| #FF0000 and #0000FF, 50/50 Lab mix | #C10088 | Lab components are averaged, then converted back to sRGB |
| #FF0000 and #0000FF, 50/50 OKLab mix | #8C53A2 | OKLab components are averaged, then converted back to sRGB |
| #FF0000 and #0000FF, 50/50 Lab mix with implicit alpha | #C10088 and alpha remains 1 | Opaque endpoints make premultiplied weighting equivalent to ordinary component interpolation |
| #FF0000 and #0000FF, 50/50 OKLab mix with implicit alpha | #8C53A2 and alpha remains 1 | Opaque endpoints make premultiplied weighting equivalent to ordinary component interpolation |
| 20% alpha red and 80% alpha blue, 50/50 sRGB mix | #3300CC80 | Alpha becomes 0.5; encoded sRGB channels use premultiplied weights before 8-bit HEX serialization |
| #000000 at 50% alpha over #FFFFFF | #808080; raw contrast on #FFFFFF = 3.9494396480491156 | Hue Codex source-over compositing uses encoded 8-bit sRGB channels before contrast |
| #000000 at 50% alpha over #FFFFFF, linear-light comparison | Approximately #BCBCBC; not used by Hue Codex opacity tools | Linear-light compositing would linearize channels before source-over math and encode back to sRGB |
| #000000 to #FFFFFF gradient sampled at 50% in sRGB | #808080 | Midpoint sample for a simple two-stop encoded sRGB gradient |
Sources and standards
These references anchor the public standards, model origins, source records, and formats used by Hue Codex. Status badges distinguish stable standards, drafts, primary sources, model sources, Hue Codex heuristics, approximations, and browser-dependent behavior.
Tools using this methodology
These Hue Codex tools link to this methodology because they depend on the formulas, assumptions, limits, or data policy described here.
Report a calculation issue
If a result does not match the documented formula or fixture, send the exact input, tool settings, displayed result, expected result, precision mode, and browser details. Please do not include private images, personal information, or secrets.