Skip to content

[BUG] Color accuracy: approximate OKLab transform + grayscale light theme charts #896

Open

Description

@rajpreetcodes

Bug Report

Platform

All platforms (Linux, macOS, Windows)

Current Behavior

In apps/desktop/src/theme.rs:150-181, the OKLab to sRGB conversion uses approximate hardcoded coefficients that produce visibly incorrect colors:

let red = linear_srgb_to_srgb(4.076_741_7 * l - 3.307_711_6 * m + 0.230_969_94 * s);
let green = linear_srgb_to_srgb(-1.268_438 * l + 2.609_757_4 * m - 0.341_319_4 * s);
let blue = linear_srgb_to_srgb(-0.004_196_086_3 * l - 0.703_418_6 * m + 1.707_614_7 * s);

These are simplified approximations of the OKLab inverse transform. For a video editor, color accuracy is critical - these inaccuracies will compound across the rendering pipeline.

Additionally, light theme chart colors are all grayscale (chroma=0):

chart_1: oklch(0.87, 0.0, 0.0),  // All chart_1 through chart_5 have chroma=0
chart_2: oklch(0.556, 0.0, 0.0),
chart_3: oklch(0.439, 0.0, 0.0),
chart_4: oklch(0.371, 0.0, 0.0),
chart_5: oklch(0.269, 0.0, 0.0),

This makes charts unusable in light mode - all bars/lines render as identical gray.

Expected Behavior

  1. Use the exact OKLab inverse transform matrix from the specification (Björn Ottosson's paper)
  2. Light theme chart colors should have distinct hues (chroma > 0)

Steps To Reproduce

  1. Run desktop app in light mode
  2. Observe chart components - all colors are identical gray
  3. Compare rendered colors against reference OKLab implementation

Recurrence Probability

Always - deterministic color math error

Additional Context

For a video editor, color fidelity is a core requirement. Consider using a vetted color space library like palette or oklab crate instead of hand-rolled approximations. The current implementation deviates from the OKLab spec.

Activity

  1. killer1panda commented on Sep 8, 2026

    @killer1panda

    I've resolved the grayscale chart colors on my fork:

    Changes

    1. Updated chart_1 through chart_5 tokens in both desktop Theme::light/Theme::dark and web styles.css from monochrome gray (chroma=0) to distinct chromatic palettes matching standard shadcn OKLCH tokens.
    2. Added unit tests in apps/desktop/src/theme.rs ensuring chart colors across light and dark modes have distinct hues.
  2. rollroyces commented on Oct 23, 2026

    @rollroyces

    Part 1 of this issue doesn't hold up — part 2 does, and it's broader

    I checked the OKLab math before acting on this, and half of it is based on a misreading of the code. Recording it so nobody spends time "fixing" correct color code.

    The inverse transform matrix is the spec matrix

    The constants in apps/desktop/src/theme.rs:163-165 are not an approximation. They're the published OKLab inverse matrix from the CSS Color 4 spec / Björn Ottosson's paper, written to 7 significant figures:

    repo value canonical value
    R 4.076_741_7 4.076_741_6621
    G 2.609_757_4 2.609_757_4011
    B 1.707_614_7 1.707_614_7010

    I compiled the repo's oklch_with_alpha alongside a reference implementation using the full-precision constants, and compared outputs across six colors spanning both axes:

    oklch(0.5,   0.1,  30): repo=[0.579200,0.294419,0.251104] ref=[0.579200,0.294419,0.251104] delta=0.00e0
    oklch(0.87,  0.15, 200): repo=[0.000000,0.945209,0.983945] ref=[0.000000,0.945209,0.983945] delta=0.00e0
    oklch(0.269, 0.2,   90): repo=[0.289957,0.064028,0.000000] ref=[0.289957,0.064028,0.000000] delta=0.00e0
    oklch(0.6,   0.05, 320): repo=[0.556399,0.466118,0.576902] ref=[0.556399,0.466118,0.576902] delta=0.00e0
    
    max delta vs canonical spec = 0.000e0
    one 8-bit color step (1/255) = 3.922e-3
    

    Delta is exactly zero — the 7-significant-figure constants round to identical f32 values, so the two implementations are bit-for-bit the same. There is no color error here, and for a video editor that's the reassuring outcome.

    So I would not swap in palette/oklab here. Adopting a crate would work, but it would be churn against correct code, and the current version already has unit tests covering the neutral endpoints. (One genuinely minor note, far below the threshold of a bug: linear_srgb_to_srgb applies the transfer function before clamping to gamut, where the spec clamps linear values first. The difference only shows at the very edges of out-of-gamut colors, and the final clamp catches it.)

    Part 2 is real, and it affects both themes

    chroma = 0.0 makes the hue argument mathematically irrelevant — a and b both collapse to zero, so r == g == b exactly:

    chart_1 oklch(0.87, 0.0, 0.0)  -> rgb=[0.8314,0.8314,0.8314]
    chart_2 oklch(0.556, 0.0, 0.0) -> rgb=[0.4515,0.4515,0.4515]
    

    The issue scopes this to the light theme, but chart_1 through chart_5 are all chroma: 0.0 in both blocks — lines 82-86 and 121-125. So dark mode is equally colorless.

    For reference, shadcn/ui's default theme uses distinct hues for these (oklch(0.646 0.222 41.116) and friends), so the grayscale values look like a deliberate placeholder rather than an oversight.

    Why I stopped here

    I don't think I should push a specific palette. No chart component is rendered in the desktop app yet, so there are no pixels to validate a choice against, and picking hues is a design decision rather than a bug fix — which is exactly the kind of thing the current contribution freeze is about.

    If you want the charts functional, the useful next step is a short decision on which values to use (shadcn defaults, a brand-derived set, or the brand/ directory if those colors are meant to carry through). Happy to implement whatever you pick.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions