Re: [csswg-drafts] [css-color-4] Evaluate static gamut mapping algorithms for oklab/oklch spaces (#10579)

This can help as a starting point for pseudo-code for the raytrace algorithm.

To ensure better performance, it is assumed that gamut mapping between the supported CSS gamuts (sRGB, Display P3, and
Rec. 2020) is performed with direct conversions between the linear RGB space to Oklab using matrices that combine the
translation of linear RGB to XYZ and XYZ to LMS. These can be done in separate steps, but combining these
calculations in one matrix can reduce the number of multiplications.

RGB gamuts are assumed to be simple rectangular prisms, positioned at [0, 0, 0] extending in the positive direction.
While there is no requirement that the gamut be limited to [1, 1, 1], we will assume SDR lightness, and assume max
values of [1, 1, 1] equalling OkLCh [1, 0, 0]. Colors that exceed the minimum OkLCh lightness of 0 and 1 would exceed
this range and be clamped to black or white, respectively. The important thing to note is that the lightness check
corresponds with the RGB constraints.

The ray trace algorithm is based on the slab method (https://en.wikipedia.org/wiki/Slab_method).

1. Assume `start` to be an anchor within the RGB gamut and `end` to be the color we wish to locate on the RGB gamut surface.
2. Assume `bmin` and `bmax` to be arrays with the gamut's lower and upper bounds, respectively.
3. Set `tfar` to infinity or some very large number.
4. Set `tnear` to -infinity or some very small number.
5. Let `direction` be a 3-element array.
6. For `(i = 0; i < 3; i++)`:
    1. `a = start[i]`
    2. `b = start[i]`
    3. `d = b - a`
    4. store `d` in `direction[i]`
    5. if `d != 0`:
        6. `inv_d = 1 / d`
        7. `t1 = (bmin[i] - a) * inv_d`
        8. `t2 = (bmax[i] - a) * inv_d`
        9. `tnear = max(min(t1, t2), tnear)`
        10. `tfar = min(max(t1, t2), tfar)`
    6. else if `a < bmin[i]` or `a > bmax[i]` return an indictation that no intersection was found
7. If `tnear > tfar` or `tfar < 0`, return an indication that no intersection was found
8. If `tnear < 0`, set tnear to tfar, favoring the first intersection in the direction `start` -> `end`
9. If `tnear` is infinite (or matches the initial very large value), return an indication that no intersection was found.
10. Return the intersection by returning each value in start such that `start[i] + direction[i] * tnear`.

To perform the ray trace gamut mapping approach:

1. If `orign` is within the target CSS gamut, return `orign`.
2. Convert `orign` to OkLCh as `oklch_orign`.
3. If `oklch_orign` lightness exceeds 1, white is returned.
4. If `oklch_orign` lightness is below 0, black is returned.
5. Lightness and hue are saved for later use as `l_origin` and `h_origin`.
6. Using `l_orign`, 0, and `h_origin` create an achromatic OkLCh color and convert it to the linear RGB gamut as `anchor`.
7. `oklch_orign` is then converted to the linear RGB gamut as `rgb_origin`
8. Set a lower threshold of 1e-6 as `low`.
9. Set a higher threshold of `1 - low` as high.
10. Store the current `rgb_origin` as `last`
11. For `(i = 0; i < 4; i++)`:
    1. If `i > 0`:
        1. Convert `rgb_origin` to OkLCh and set lightness and hue to `l_origin` and `h_origin` respectively.
        2. Convert back to linear RGB and store in `rgb_origin`
    2. Cast a ray from the `anchor` to `rgb_origin` and save the gamut intersection as `intersection`.
    3. If `intersection` was not found, update `rgb_origin` with `last` and exit the loop.
    4. For each component in `rgb_origin`, if every value is between the `low` and `high` threshold, update `anchor` with `rgb_origin`. An anchor closer to the gamut surface.
    5. Update `rgb_origin` and `last` with `intersection`.
12. Clip `rgb_origin` to gamut.
13. Convert `rgb_origin` back to the color space of `origin` and update `origin`.


-- 
GitHub Notification of comment by facelessuser
Please view or discuss this issue at https://github.com/w3c/csswg-drafts/issues/10579#issuecomment-3787972702 using your GitHub account


-- 
Sent via github-notify-ml as configured in https://github.com/w3c/github-notify-ml-config

Received on Friday, 23 January 2026 03:22:00 UTC