Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/servo/components/style/color/gamut/   (Firefox Browser Version 153.0.1©)  Datei vom 27.6.2026 mit Größe 11 kB image not shown  

Quelle  raytrace.rs

  Sprache: Rust
 

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */


//! Gamut mapping - Raytrace algorithm.
//! <https://drafts.csswg.org/css-color-4/#gamut-mapping>

use crate::color::{gamut::MIN_PRECISION, AbsoluteColor, ColorComponents, ColorSpace};

impl AbsoluteColor {
    /// 13.2.5. The Ray Trace Gamut Mapping
    /// <https://drafts.csswg.org/css-color-4/#GMA-Raytrace>
    pub fn gamut_map_raytrace(&self, dest_color_space: ColorSpace) -> Self {
        macro_rules! in_range {
            ($l:expr, $c:expr, $h:expr) => {{
                $c >= $l && $c <= $h
            }};
        }

        const MIN_L: f32 = MIN_PRECISION;
        const MAX_L: f32 = 1.0;

        // Get the linear version of the destination color space;
        // else, use the same space for the destination.
        // NOTE that this will cause temporary approximations for wide-gamut
        // RGB spaces beyond DisplayP3 (A98RGB, ProPhoto, and Rec2020) that
        // currently don't have linear versions built-out for them.
        // See comments/notes on [`ColorSpace::get_linear_color_space()`].
        let dest_linear_color_space = dest_color_space
            .get_linear_color_space()
            .unwrap_or(dest_color_space);

        // 1. if destination has no gamut limits (XYZ-D65, XYZ-D50, Lab, LCH,
        //    Oklab, OkLCh) convert origin to destination and return it as the
        //    gamut mapped color
        if matches!(
            dest_color_space,
            ColorSpace::Lab
                | ColorSpace::Lch
                | ColorSpace::Oklab
                | ColorSpace::Oklch
                | ColorSpace::XyzD50
                | ColorSpace::XyzD65
        ) {
            return self.to_color_space(dest_color_space);
        }

        // 2. let `origin_OkLCh` be `origin` converted from `origin color
        //    space` to the OkLCh color space
        let origin_oklch = self.to_color_space(ColorSpace::Oklch);

        // 3. if the Lightness of `origin_OkLCh` is greater than or equal to
        //    100%, convert `oklab(1 0 0 / origin.alpha)` to `destination` and
        //    return it as the gamut mapped color
        if origin_oklch.components.0 >= MAX_L {
            return AbsoluteColor::new(ColorSpace::Oklab, 1.00.00.0self.alpha)
                .to_color_space(dest_color_space);
        }

        // 4. if the Lightness of `origin_OkLCh` is less than than or equal to
        //    0%, convert `oklab(0 0 0 / origin.alpha)` to `destination` and
        //    return it as the gamut mapped color
        if origin_oklch.components.0 <= MIN_L {
            return AbsoluteColor::new(ColorSpace::Oklab, 0.00.00.0self.alpha)
                .to_color_space(dest_color_space);
        }

        // 5. let `l_origin` be the OkLCh lightness component of `origin_OkLCh`
        // 6. let `h_origin` be the OkLCh hue component of `origin_OkLCh`
        let ColorComponents(l_origin, _, h_origin) = origin_oklch.components;

        // 7. let `anchor` be an achromatic OkLCh color formed with `l_origin`
        //    as lightness, 0 as chroma, and `h_origin` as hue, converted to
        //    the linear-light form of destination
        let mut anchor = AbsoluteColor::new(ColorSpace::Oklch, l_origin, 0.0, h_origin, self.alpha)
            .to_color_space(dest_linear_color_space);

        // 8. let `origin_rgb` be `origin_OkLCh` converted to the linear-light
        //    form of destination
        let mut origin_rgb = origin_oklch.to_color_space(dest_linear_color_space);

        // 9. if origin_rgb is not in gamut
        if !origin_rgb.in_gamut() {
            // 9.1. let `low` be 1E-6 [^1]
            const LOW: f32 = 1.0e-6;

            // 9.2 let `high` be 1.0 - `low` [^2]
            const HIGH: f32 = 1.0 - LOW;

            // 9.3 let `last` be `origin_rgb`
            let mut last = origin_rgb;

            // We're doing 4 cycles of ray tracing.
            // 9.4 `for (i=0; i<4; i++)`:
            for i in 0..4 {
                // 9.4.1. if (`i > 0`)
                if i > 0 {
                    // 9.4.1.1. let `current_OkLCh` be `origin_rgb` converted to OkLCh
                    let mut current_oklch = origin_rgb.to_color_space(ColorSpace::Oklch);

                    // 9.4.1.2. let the lightness of `current_OkLCh` be `l_origin`
                    current_oklch.components.0 = l_origin;
                    // 9.4.1.3. let the hue of `current_OkLCh` be `h_origin` [^3]
                    current_oklch.components.2 = h_origin;

                    // 9.4.1.4. let `origin_rgb` be `current_OkLCh` converted to the
                    //         linear-light form of destination
                    origin_rgb = current_oklch.to_color_space(dest_linear_color_space);
                }

                // 9.4.2. **Cast a ray** from `anchor` to `origin_rgb` and let
                //        `intersection` be the intersection of this ray with the
                //        gamut boundary
                let intersection = Self::cast_ray(&anchor.components, &origin_rgb.components);

                // 9.4.3. if an intersection was not found, let `origin_rgb` be
                //        `last` and exit the loop [^5]
                let Some(intersection) = intersection else {
                    origin_rgb = last;
                    break;
                };

                // 9.4.4. if (`i > 0`) AND (each component of `origin_rgb` is
                //        between `low` and `high`) then let `anchor` be
                //        `origin_rgb` [^4]
                if (i > 0)
                    && in_range!(LOW, origin_rgb.components.0, HIGH)
                    && in_range!(LOW, origin_rgb.components.1, HIGH)
                    && in_range!(LOW, origin_rgb.components.2, HIGH)
                {
                    anchor = origin_rgb;
                }

                // 9.4.5. let `origin_rgb` be `intersection`
                origin_rgb.components = intersection;

                // 9.4.6. let `last` be `intersection`
                last.components = intersection;
            }
        }

        // 10. let clip(color) be a function which converts color to
        //     destination, clamps each component to the bounds of the
        //     reference range for that component, and returns the result
        //     See [`Self::clip`] and [`Self::clip_to_dest_space`].

        // NOTE: in the 2026-02-27 draft, this line doesn't make sense.
        //       I've posted a question/issue to the CSS Color 4 team about it.
        //       See <https://github.com/w3c/csswg-drafts/issues/10579#issuecomment-4122677476>
        //       Isaac (@facelessuser) replied that it is ambiguous, but ATOW this hasn't
        //       been fixed in the working copy pseudo-code.
        //       See <https://github.com/w3c/csswg-drafts/issues/10579#issuecomment-4123490623>
        // 11. set clipped to clip(current)
        //
        // In the meantime, this was the logic from the 2026-02-02 draft that worked:
        // 13. let `clipped` be `origin_rgb` clipped to gamut (components in
        //     the range 0 to 1), thus trimming off any noise due to floating
        //     point inaccuracy
        let clipped = origin_rgb.clip_to_dest_space(dest_color_space);

        // 12. return `clipped` as the gamut mapped color
        clipped
    }

    /// To **cast a ray** through a linear-light RGB space from `start` to
    /// `end` (in gamut mapping, `start` is an anchor within the RGB gamut and
    /// `end` is the gamut mapped color, on the cubical gamut surface)
    /// <https://drafts.csswg.org/css-color-4/#GMA-Raytrace>
    fn cast_ray(start: &ColorComponents, end: &ColorComponents) -> Option<ColorComponents> {
        // 1. let `bmin` and `bmax` be 3-element arrays with the gamut’s lower
        //    and upper bounds, respectively [^6]
        // NOTE: assuming these are always tied to RGB bounds [0.0, 1.0]
        let bmin = [0.00.00.0];
        let bmax = [1.01.01.0];

        // 2. let `tfar` be `infinity` (or some very large number)
        let mut tfar = std::f32::INFINITY;

        // 3. let `tnear` be `-infinity` (or some very large, negative number)
        let mut tnear = std::f32::NEG_INFINITY;

        // 4. let `direction` be a 3-element array
        let mut direction = [0.00.00.0];

        // reshape start and end so we can iterate over them
        let start_array = start.to_array();
        let end_array = end.to_array();

        // 5. `for (i = 0; i < 3; i++)`:
        for i in 0..3 {
            // 5.1. let `a` be `start[i]`
            let a = start_array[i];

            // 5.2. let `b` be `end[i]`
            let b = end_array[i];

            // 5.3. let `d` be `b - a`
            let d = b - a;

            // 5.4. let `direction[i]` be `d`
            direction[i] = d;

            // 5.5. if abs(d) > MIN_THRESHOLD:
            // NOTE the 2026-02-27 spec is incorrect; it uses less-than -- should be greater-than.
            //      Reference impls colorjs.io and ColorAide both use greater-than.
            //      Issue reported to CSS Color 4 team; yet to be fixed in working draft ATOW.
            //      See <https://github.com/w3c/csswg-drafts/issues/10579#issuecomment-4122988782>
            // NOTE the spec and reference implementations all assume f64 precision, where
            //      MIN_THRESHOLD is 1e-12 currently in the spec. This is too precise for f32.
            //      Testing by working group members revealed the issue, and recommended
            //      loosening the precision to accommodate f32 properly by using 1e-6.
            //      See <https://github.com/w3c/csswg-drafts/issues/10579#issuecomment-4666657910>.
            const MIN_THRESHOLD: f32 = 1e-6;
            if d.abs() > MIN_THRESHOLD {
                // 5.5.1. let `inv_d` be `1 / d`
                let inv_d = 1.0 / d;

                // 5.5.2. let `t1` be `(bmin[i] - a) * inv_d`
                let t1 = (bmin[i] - a) * inv_d;

                // 5.5.3. let `t2` be `(bmax[i] - a) * inv_d`
                let t2 = (bmax[i] - a) * inv_d;

                // 5.5.4. let `tnear` be `max(min(t1, t2), tnear)`
                tnear = t1.min(t2).max(tnear);

                // 5.5.5. let `tfar` be `min(max(t1, t2), tfar)`
                tfar = t1.max(t2).min(tfar);
            }
            // 5.6. else if (`a < bmin[i]` or `a > bmax[i]`)
            //      * return `INTERSECTION NOT FOUND`
            else if a < bmin[i] || a > bmax[i] {
                return None;
            }
        }

        // 6. if (`tnear > tfar` or `tfar < 0`)
        //    * return `INTERSECTION NOT FOUND`
        if (tnear > tfar) || (tfar < 0.0) {
            return None;
        }

        // 7. if `tnear < 0`
        //    * let `tnear` be `tfar` [^7]
        if tnear < 0.0 {
            tnear = tfar;
        }

        // 8. if tnear is infinite (or matches the initial very large value)
        //    * return `INTERSECTION NOT FOUND`
        if tnear.is_infinite() {
            return None;
        }

        let mut result = [0.00.00.0];
        // 9. for (`i = 0; i < 3; i++`):
        //    * let `result[i]` be `start[i] + direction[i] * tnear`
        for i in 0..3 {
            result[i] = start_array[i] + direction[i] * tnear;
        }

        // 10. return `result`
        Some(ColorComponents(result[0], result[1], result[2]))
    }
}

Messung V0.5 in Prozent
C=59 H=98 G=80

¤ Dauer der Verarbeitung: 0.27 Sekunden  (vorverarbeitet am  2026-08-25) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.