Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  convert.rs   Sprache: unbekannt

 
/* 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/. */

//! Color conversion algorithms.
//!
//! Algorithms, matrices and constants are from the [color-4] specification,
//! unless otherwise specified:
//!
//! https://drafts.csswg.org/css-color-4/#color-conversion-code
//!
//! NOTE: Matrices has to be transposed from the examples in the spec for use
//! with the `euclid` library.

use crate::color::ColorComponents;
use crate::values::normalize;

type Transform = euclid::default::Transform3D<f32>;
type Vector = euclid::default::Vector3D<f32>;

/// Normalize hue into [0, 360).
#[inline]
pub fn normalize_hue(hue: f32) -> f32 {
    hue - 360. * (hue / 360.).floor()
}

/// Calculate the hue from RGB components and return it along with the min and
/// max RGB values.
#[inline]
fn rgb_to_hue_min_max(red: f32, green: f32, blue: f32) -> (f32, f32, f32) {
    let max = red.max(green).max(blue);
    let min = red.min(green).min(blue);

    let delta = max - min;

    let hue = if delta != 0.0 {
        60.0 * if max == red {
            (green - blue) / delta + if green < blue { 6.0 } else { 0.0 }
        } else if max == green {
            (blue - red) / delta + 2.0
        } else {
            (red - green) / delta + 4.0
        }
    } else {
        f32::NAN
    };

    (hue, min, max)
}

/// Convert from HSL notation to RGB notation.
/// https://drafts.csswg.org/css-color-4/#hsl-to-rgb
#[inline]
pub fn hsl_to_rgb(from: &ColorComponents) -> ColorComponents {
    fn hue_to_rgb(t1: f32, t2: f32, hue: f32) -> f32 {
        let hue = normalize_hue(hue);

        if hue * 6.0 < 360.0 {
            t1 + (t2 - t1) * hue / 60.0
        } else if hue * 2.0 < 360.0 {
            t2
        } else if hue * 3.0 < 720.0 {
            t1 + (t2 - t1) * (240.0 - hue) / 60.0
        } else {
            t1
        }
    }

    // Convert missing components to 0.0.
    let ColorComponents(hue, saturation, lightness) = from.map(normalize);
    let saturation = saturation / 100.0;
    let lightness = lightness / 100.0;

    let t2 = if lightness <= 0.5 {
        lightness * (saturation + 1.0)
    } else {
        lightness + saturation - lightness * saturation
    };
    let t1 = lightness * 2.0 - t2;

    ColorComponents(
        hue_to_rgb(t1, t2, hue + 120.0),
        hue_to_rgb(t1, t2, hue),
        hue_to_rgb(t1, t2, hue - 120.0),
    )
}

/// Convert from RGB notation to HSL notation.
/// https://drafts.csswg.org/css-color-4/#rgb-to-hsl
pub fn rgb_to_hsl(from: &ColorComponents) -> ColorComponents {
    let ColorComponents(red, green, blue) = *from;

    let (hue, min, max) = rgb_to_hue_min_max(red, green, blue);

    let lightness = (min + max) / 2.0;
    let delta = max - min;

    let saturation = if delta != 0.0 {
        if lightness == 0.0 || lightness == 1.0 {
            0.0
        } else {
            (max - lightness) / lightness.min(1.0 - lightness)
        }
    } else {
        0.0
    };

    ColorComponents(hue, saturation * 100.0, lightness * 100.0)
}

/// Convert from HWB notation to RGB notation.
/// https://drafts.csswg.org/css-color-4/#hwb-to-rgb
#[inline]
pub fn hwb_to_rgb(from: &ColorComponents) -> ColorComponents {
    // Convert missing components to 0.0.
    let ColorComponents(hue, whiteness, blackness) = from.map(normalize);

    let whiteness = whiteness / 100.0;
    let blackness = blackness / 100.0;

    if whiteness + blackness >= 1.0 {
        let gray = whiteness / (whiteness + blackness);
        return ColorComponents(gray, gray, gray);
    }

    let x = 1.0 - whiteness - blackness;
    hsl_to_rgb(&ColorComponents(hue, 100.0, 50.0)).map(|v| v * x + whiteness)
}

/// Convert from RGB notation to HWB notation.
/// https://drafts.csswg.org/css-color-4/#rgb-to-hwb
#[inline]
pub fn rgb_to_hwb(from: &ColorComponents) -> ColorComponents {
    let ColorComponents(red, green, blue) = *from;

    let (hue, min, max) = rgb_to_hue_min_max(red, green, blue);

    let whiteness = min;
    let blackness = 1.0 - max;

    ColorComponents(hue, whiteness * 100.0, blackness * 100.0)
}

/// Calculate an epsilon for a specified range.
#[inline]
pub fn epsilon_for_range(min: f32, max: f32) -> f32 {
    (max - min) / 1.0e5
}

/// Convert from the rectangular orthogonal to the cylindrical polar coordinate
/// system. This is used to convert (ok)lab to (ok)lch.
/// <https://drafts.csswg.org/css-color-4/#lab-to-lch>
#[inline]
pub fn orthogonal_to_polar(from: &ColorComponents, e: f32) -> ColorComponents {
    let ColorComponents(lightness, a, b) = *from;

    let chroma = (a * a + b * b).sqrt();

    let hue = if a.abs() < e && b.abs() < e {
        // For extremely small values of a and b ... the reported hue angle
        // swinging about wildly and being essentially random ... this means
        // the hue is powerless, and treated as missing when converted into LCH
        // or Oklch.
        f32::NAN
    } else if chroma.abs() < e {
        // Very small chroma values make the hue component powerless.
        f32::NAN
    } else {
        normalize_hue(b.atan2(a).to_degrees())
    };

    ColorComponents(lightness, chroma, hue)
}

/// Convert from the cylindrical polar to the rectangular orthogonal coordinate
/// system. This is used to convert (ok)lch to (ok)lab.
/// <https://drafts.csswg.org/css-color-4/#lch-to-lab>
#[inline]
pub fn polar_to_orthogonal(from: &ColorComponents) -> ColorComponents {
    let ColorComponents(lightness, chroma, hue) = *from;

    // A missing hue component results in an achromatic color.
    if hue.is_nan() {
        return ColorComponents(lightness, 0.0, 0.0);
    }

    let hue = hue.to_radians();
    let a = chroma * hue.cos();
    let b = chroma * hue.sin();

    ColorComponents(lightness, a, b)
}

#[inline]
fn transform(from: &ColorComponents, mat: &Transform) -> ColorComponents {
    let result = mat.transform_vector3d(Vector::new(from.0, from.1, from.2));
    ColorComponents(result.x, result.y, result.z)
}

fn xyz_d65_to_xyz_d50(from: &ColorComponents) -> ColorComponents {
    #[rustfmt::skip]
    const MAT: Transform = Transform::new(
         1.0479298208405488,    0.029627815688159344, -0.009243058152591178, 0.0,
         0.022946793341019088,  0.990434484573249,     0.015055144896577895, 0.0,
        -0.05019222954313557,  -0.01707382502938514,   0.7518742899580008,   0.0,
         0.0,                   0.0,                   0.0,                  1.0,
    );

    transform(from, &MAT)
}

fn xyz_d50_to_xyz_d65(from: &ColorComponents) -> ColorComponents {
    #[rustfmt::skip]
    const MAT: Transform = Transform::new(
         0.9554734527042182,   -0.028369706963208136,  0.012314001688319899, 0.0,
        -0.023098536874261423,  1.0099954580058226,   -0.020507696433477912, 0.0,
         0.0632593086610217,    0.021041398966943008,  1.3303659366080753,   0.0,
         0.0,                   0.0,                   0.0,                  1.0,
    );

    transform(from, &MAT)
}

/// A reference white that is used during color conversion.
pub enum WhitePoint {
    /// D50 white reference.
    D50,
    /// D65 white reference.
    D65,
}

impl WhitePoint {
    const fn values(&self) -> ColorComponents {
        // <https://drafts.csswg.org/css-color-4/#color-conversion-code>
        match self {
            // [0.3457 / 0.3585, 1.00000, (1.0 - 0.3457 - 0.3585) / 0.3585]
            WhitePoint::D50 => ColorComponents(0.9642956764295677, 1.0, 0.8251046025104602),
            // [0.3127 / 0.3290, 1.00000, (1.0 - 0.3127 - 0.3290) / 0.3290]
            WhitePoint::D65 => ColorComponents(0.9504559270516716, 1.0, 1.0890577507598784),
        }
    }
}

fn convert_white_point(from: WhitePoint, to: WhitePoint, components: &mut ColorComponents) {
    match (from, to) {
        (WhitePoint::D50, WhitePoint::D65) => *components = xyz_d50_to_xyz_d65(components),
        (WhitePoint::D65, WhitePoint::D50) => *components = xyz_d65_to_xyz_d50(components),
        _ => {},
    }
}

/// A trait that allows conversion of color spaces to and from XYZ coordinate
/// space with a specified white point.
///
/// Allows following the specified method of converting between color spaces:
/// - Convert to values to sRGB linear light.
/// - Convert to XYZ coordinate space.
/// - Adjust white point to target white point.
/// - Convert to sRGB linear light in target color space.
/// - Convert to sRGB gamma encoded in target color space.
///
/// https://drafts.csswg.org/css-color-4/#color-conversion
pub trait ColorSpaceConversion {
    /// The white point that the implementer is represented in.
    const WHITE_POINT: WhitePoint;

    /// Convert the components from sRGB gamma encoded values to sRGB linear
    /// light values.
    fn to_linear_light(from: &ColorComponents) -> ColorComponents;

    /// Convert the components from sRGB linear light values to XYZ coordinate
    /// space.
    fn to_xyz(from: &ColorComponents) -> ColorComponents;

    /// Convert the components from XYZ coordinate space to sRGB linear light
    /// values.
    fn from_xyz(from: &ColorComponents) -> ColorComponents;

    /// Convert the components from sRGB linear light values to sRGB gamma
    /// encoded values.
    fn to_gamma_encoded(from: &ColorComponents) -> ColorComponents;
}

/// Convert the color components from the specified color space to XYZ and
/// return the components and the white point they are in.
pub fn to_xyz<From: ColorSpaceConversion>(from: &ColorComponents) -> (ColorComponents, WhitePoint) {
    // Convert the color components where in-gamut values are in the range
    // [0 - 1] to linear light (un-companded) form.
    let result = From::to_linear_light(from);

    // Convert the color components from the source color space to XYZ.
    (From::to_xyz(&result), From::WHITE_POINT)
}

/// Convert the color components from XYZ at the given white point to the
/// specified color space.
pub fn from_xyz<To: ColorSpaceConversion>(
    from: &ColorComponents,
    white_point: WhitePoint,
) -> ColorComponents {
    let mut xyz = from.clone();

    // Convert the white point if needed.
    convert_white_point(white_point, To::WHITE_POINT, &mut xyz);

    // Convert the color from XYZ to the target color space.
    let result = To::from_xyz(&xyz);

    // Convert the color components of linear-light values in the range
    // [0 - 1] to a gamma corrected form.
    To::to_gamma_encoded(&result)
}

/// The sRGB color space.
/// https://drafts.csswg.org/css-color-4/#predefined-sRGB
pub struct Srgb;

impl Srgb {
    #[rustfmt::skip]
    const TO_XYZ: Transform = Transform::new(
        0.4123907992659595,  0.21263900587151036, 0.01933081871559185, 0.0,
        0.35758433938387796, 0.7151686787677559,  0.11919477979462599, 0.0,
        0.1804807884018343,  0.07219231536073371, 0.9505321522496606,  0.0,
        0.0,                 0.0,                 0.0,                 1.0,
    );

    #[rustfmt::skip]
    const FROM_XYZ: Transform = Transform::new(
         3.2409699419045213, -0.9692436362808798,  0.05563007969699361, 0.0,
        -1.5373831775700935,  1.8759675015077206, -0.20397695888897657, 0.0,
        -0.4986107602930033,  0.04155505740717561, 1.0569715142428786,  0.0,
         0.0,                 0.0,                 0.0,                 1.0,
    );
}

impl ColorSpaceConversion for Srgb {
    const WHITE_POINT: WhitePoint = WhitePoint::D65;

    fn to_linear_light(from: &ColorComponents) -> ColorComponents {
        from.clone().map(|value| {
            let abs = value.abs();

            if abs < 0.04045 {
                value / 12.92
            } else {
                value.signum() * ((abs + 0.055) / 1.055).powf(2.4)
            }
        })
    }

    fn to_xyz(from: &ColorComponents) -> ColorComponents {
        transform(from, &Self::TO_XYZ)
    }

    fn from_xyz(from: &ColorComponents) -> ColorComponents {
        transform(from, &Self::FROM_XYZ)
    }

    fn to_gamma_encoded(from: &ColorComponents) -> ColorComponents {
        from.clone().map(|value| {
            let abs = value.abs();

            if abs > 0.0031308 {
                value.signum() * (1.055 * abs.powf(1.0 / 2.4) - 0.055)
            } else {
                12.92 * value
            }
        })
    }
}

/// Color specified with hue, saturation and lightness components.
pub struct Hsl;

impl ColorSpaceConversion for Hsl {
    const WHITE_POINT: WhitePoint = Srgb::WHITE_POINT;

    fn to_linear_light(from: &ColorComponents) -> ColorComponents {
        Srgb::to_linear_light(&hsl_to_rgb(from))
    }

    #[inline]
    fn to_xyz(from: &ColorComponents) -> ColorComponents {
        Srgb::to_xyz(from)
    }

    #[inline]
    fn from_xyz(from: &ColorComponents) -> ColorComponents {
        Srgb::from_xyz(from)
    }

    fn to_gamma_encoded(from: &ColorComponents) -> ColorComponents {
        rgb_to_hsl(&Srgb::to_gamma_encoded(from))
    }
}

/// Color specified with hue, whiteness and blackness components.
pub struct Hwb;

impl ColorSpaceConversion for Hwb {
    const WHITE_POINT: WhitePoint = Srgb::WHITE_POINT;

    fn to_linear_light(from: &ColorComponents) -> ColorComponents {
        Srgb::to_linear_light(&hwb_to_rgb(from))
    }

    #[inline]
    fn to_xyz(from: &ColorComponents) -> ColorComponents {
        Srgb::to_xyz(from)
    }

    #[inline]
    fn from_xyz(from: &ColorComponents) -> ColorComponents {
        Srgb::from_xyz(from)
    }

    fn to_gamma_encoded(from: &ColorComponents) -> ColorComponents {
        rgb_to_hwb(&Srgb::to_gamma_encoded(from))
    }
}

/// The same as sRGB color space, except the transfer function is linear light.
/// https://drafts.csswg.org/css-color-4/#predefined-sRGB-linear
pub struct SrgbLinear;

impl ColorSpaceConversion for SrgbLinear {
    const WHITE_POINT: WhitePoint = Srgb::WHITE_POINT;

    fn to_linear_light(from: &ColorComponents) -> ColorComponents {
        // Already in linear light form.
        from.clone()
    }

    fn to_xyz(from: &ColorComponents) -> ColorComponents {
        Srgb::to_xyz(from)
    }

    fn from_xyz(from: &ColorComponents) -> ColorComponents {
        Srgb::from_xyz(from)
    }

    fn to_gamma_encoded(from: &ColorComponents) -> ColorComponents {
        // Stay in linear light form.
        from.clone()
    }
}

/// The Display-P3 color space.
/// https://drafts.csswg.org/css-color-4/#predefined-display-p3
pub struct DisplayP3;

impl DisplayP3 {
    #[rustfmt::skip]
    const TO_XYZ: Transform = Transform::new(
        0.48657094864821626, 0.22897456406974884, 0.0,                  0.0,
        0.26566769316909294, 0.6917385218365062,  0.045113381858902575, 0.0,
        0.1982172852343625,  0.079286914093745,   1.0439443689009757,   0.0,
        0.0,                 0.0,                 0.0,                  1.0,
    );

    #[rustfmt::skip]
    const FROM_XYZ: Transform = Transform::new(
         2.4934969119414245,  -0.829488969561575,    0.035845830243784335, 0.0,
        -0.9313836179191236,   1.7626640603183468,  -0.07617238926804171,  0.0,
        -0.40271078445071684,  0.02362468584194359,  0.9568845240076873,   0.0,
         0.0,                  0.0,                  0.0,                  1.0,
    );
}

impl ColorSpaceConversion for DisplayP3 {
    const WHITE_POINT: WhitePoint = WhitePoint::D65;

    fn to_linear_light(from: &ColorComponents) -> ColorComponents {
        Srgb::to_linear_light(from)
    }

    fn to_xyz(from: &ColorComponents) -> ColorComponents {
        transform(from, &Self::TO_XYZ)
    }

    fn from_xyz(from: &ColorComponents) -> ColorComponents {
        transform(from, &Self::FROM_XYZ)
    }

    fn to_gamma_encoded(from: &ColorComponents) -> ColorComponents {
        Srgb::to_gamma_encoded(from)
    }
}

/// The a98-rgb color space.
/// https://drafts.csswg.org/css-color-4/#predefined-a98-rgb
pub struct A98Rgb;

impl A98Rgb {
    #[rustfmt::skip]
    const TO_XYZ: Transform = Transform::new(
        0.5766690429101308,  0.29734497525053616, 0.027031361386412378, 0.0,
        0.18555823790654627, 0.627363566255466,   0.07068885253582714,  0.0,
        0.18822864623499472, 0.07529145849399789, 0.9913375368376389,   0.0,
        0.0,                 0.0,                 0.0,                  1.0,
    );

    #[rustfmt::skip]
    const FROM_XYZ: Transform = Transform::new(
         2.041587903810746,  -0.9692436362808798,   0.013444280632031024, 0.0,
        -0.5650069742788596,  1.8759675015077206,  -0.11836239223101824,  0.0,
        -0.3447313507783295,  0.04155505740717561,  1.0151749943912054,   0.0,
         0.0,                 0.0,                  0.0,                  1.0,
    );
}

impl ColorSpaceConversion for A98Rgb {
    const WHITE_POINT: WhitePoint = WhitePoint::D65;

    fn to_linear_light(from: &ColorComponents) -> ColorComponents {
        from.clone().map(|v| v.signum() * v.abs().powf(2.19921875))
    }

    fn to_xyz(from: &ColorComponents) -> ColorComponents {
        transform(from, &Self::TO_XYZ)
    }

    fn from_xyz(from: &ColorComponents) -> ColorComponents {
        transform(from, &Self::FROM_XYZ)
    }

    fn to_gamma_encoded(from: &ColorComponents) -> ColorComponents {
        from.clone()
            .map(|v| v.signum() * v.abs().powf(0.4547069271758437))
    }
}

/// The ProPhoto RGB color space.
/// https://drafts.csswg.org/css-color-4/#predefined-prophoto-rgb
pub struct ProphotoRgb;

impl ProphotoRgb {
    #[rustfmt::skip]
    const TO_XYZ: Transform = Transform::new(
        0.7977604896723027,  0.2880711282292934,     0.0,                0.0,
        0.13518583717574031, 0.7118432178101014,     0.0,                0.0,
        0.0313493495815248,  0.00008565396060525902, 0.8251046025104601, 0.0,
        0.0,                 0.0,                    0.0,                1.0,
    );

    #[rustfmt::skip]
    const FROM_XYZ: Transform = Transform::new(
         1.3457989731028281,  -0.5446224939028347,  0.0,                0.0,
        -0.25558010007997534,  1.5082327413132781,  0.0,                0.0,
        -0.05110628506753401,  0.02053603239147973, 1.2119675456389454, 0.0,
         0.0,                  0.0,                 0.0,                1.0,
    );
}

impl ColorSpaceConversion for ProphotoRgb {
    const WHITE_POINT: WhitePoint = WhitePoint::D50;

    fn to_linear_light(from: &ColorComponents) -> ColorComponents {
        from.clone().map(|value| {
            const ET2: f32 = 16.0 / 512.0;

            let abs = value.abs();

            if abs <= ET2 {
                value / 16.0
            } else {
                value.signum() * abs.powf(1.8)
            }
        })
    }

    fn to_xyz(from: &ColorComponents) -> ColorComponents {
        transform(from, &Self::TO_XYZ)
    }

    fn from_xyz(from: &ColorComponents) -> ColorComponents {
        transform(from, &Self::FROM_XYZ)
    }

    fn to_gamma_encoded(from: &ColorComponents) -> ColorComponents {
        const ET: f32 = 1.0 / 512.0;

        from.clone().map(|v| {
            let abs = v.abs();
            if abs >= ET {
                v.signum() * abs.powf(1.0 / 1.8)
            } else {
                16.0 * v
            }
        })
    }
}

/// The Rec.2020 color space.
/// https://drafts.csswg.org/css-color-4/#predefined-rec2020
pub struct Rec2020;

impl Rec2020 {
    const ALPHA: f32 = 1.09929682680944;
    const BETA: f32 = 0.018053968510807;

    #[rustfmt::skip]
    const TO_XYZ: Transform = Transform::new(
        0.6369580483012913,  0.26270021201126703,  0.0,                  0.0,
        0.14461690358620838, 0.677998071518871,    0.028072693049087508, 0.0,
        0.16888097516417205, 0.059301716469861945, 1.0609850577107909,   0.0,
        0.0,                 0.0,                  0.0,                  1.0,
    );

    #[rustfmt::skip]
    const FROM_XYZ: Transform = Transform::new(
         1.7166511879712676, -0.666684351832489,    0.017639857445310915, 0.0,
        -0.3556707837763924,  1.616481236634939,   -0.042770613257808655, 0.0,
        -0.2533662813736598,  0.01576854581391113,  0.942103121235474,    0.0,
         0.0,                 0.0,                  0.0,                  1.0,
    );
}

impl ColorSpaceConversion for Rec2020 {
    const WHITE_POINT: WhitePoint = WhitePoint::D65;

    fn to_linear_light(from: &ColorComponents) -> ColorComponents {
        from.clone().map(|value| {
            let abs = value.abs();

            if abs < Self::BETA * 4.5 {
                value / 4.5
            } else {
                value.signum() * ((abs + Self::ALPHA - 1.0) / Self::ALPHA).powf(1.0 / 0.45)
            }
        })
    }

    fn to_xyz(from: &ColorComponents) -> ColorComponents {
        transform(from, &Self::TO_XYZ)
    }

    fn from_xyz(from: &ColorComponents) -> ColorComponents {
        transform(from, &Self::FROM_XYZ)
    }

    fn to_gamma_encoded(from: &ColorComponents) -> ColorComponents {
        from.clone().map(|v| {
            let abs = v.abs();

            if abs > Self::BETA {
                v.signum() * (Self::ALPHA * abs.powf(0.45) - (Self::ALPHA - 1.0))
            } else {
                4.5 * v
            }
        })
    }
}

/// A color in the XYZ coordinate space with a D50 white reference.
/// https://drafts.csswg.org/css-color-4/#predefined-xyz
pub struct XyzD50;

impl ColorSpaceConversion for XyzD50 {
    const WHITE_POINT: WhitePoint = WhitePoint::D50;

    fn to_linear_light(from: &ColorComponents) -> ColorComponents {
        from.clone()
    }

    fn to_xyz(from: &ColorComponents) -> ColorComponents {
        from.clone()
    }

    fn from_xyz(from: &ColorComponents) -> ColorComponents {
        from.clone()
    }

    fn to_gamma_encoded(from: &ColorComponents) -> ColorComponents {
        from.clone()
    }
}

/// A color in the XYZ coordinate space with a D65 white reference.
/// https://drafts.csswg.org/css-color-4/#predefined-xyz
pub struct XyzD65;

impl ColorSpaceConversion for XyzD65 {
    const WHITE_POINT: WhitePoint = WhitePoint::D65;

    fn to_linear_light(from: &ColorComponents) -> ColorComponents {
        from.clone()
    }

    fn to_xyz(from: &ColorComponents) -> ColorComponents {
        from.clone()
    }

    fn from_xyz(from: &ColorComponents) -> ColorComponents {
        from.clone()
    }

    fn to_gamma_encoded(from: &ColorComponents) -> ColorComponents {
        from.clone()
    }
}

/// The Lab color space.
/// https://drafts.csswg.org/css-color-4/#specifying-lab-lch
pub struct Lab;

impl Lab {
    const KAPPA: f32 = 24389.0 / 27.0;
    const EPSILON: f32 = 216.0 / 24389.0;
}

impl ColorSpaceConversion for Lab {
    const WHITE_POINT: WhitePoint = WhitePoint::D50;

    fn to_linear_light(from: &ColorComponents) -> ColorComponents {
        // No need for conversion.
        from.clone()
    }

    /// Convert a CIELAB color to XYZ as specified in [1] and [2].
    ///
    /// [1]: https://drafts.csswg.org/css-color/#lab-to-predefined
    /// [2]: https://drafts.csswg.org/css-color/#color-conversion-code
    fn to_xyz(from: &ColorComponents) -> ColorComponents {
        let ColorComponents(lightness, a, b) = *from;

        let f1 = (lightness + 16.0) / 116.0;
        let f0 = f1 + a / 500.0;
        let f2 = f1 - b / 200.0;

        let f0_cubed = f0 * f0 * f0;
        let x = if f0_cubed > Self::EPSILON {
            f0_cubed
        } else {
            (116.0 * f0 - 16.0) / Self::KAPPA
        };

        let y = if lightness > Self::KAPPA * Self::EPSILON {
            let v = (lightness + 16.0) / 116.0;
            v * v * v
        } else {
            lightness / Self::KAPPA
        };

        let f2_cubed = f2 * f2 * f2;
        let z = if f2_cubed > Self::EPSILON {
            f2_cubed
        } else {
            (116.0 * f2 - 16.0) / Self::KAPPA
        };

        ColorComponents(x, y, z) * Self::WHITE_POINT.values()
    }

    /// Convert an XYZ color to LAB as specified in [1] and [2].
    ///
    /// [1]: https://drafts.csswg.org/css-color/#rgb-to-lab
    /// [2]: https://drafts.csswg.org/css-color/#color-conversion-code
    fn from_xyz(from: &ColorComponents) -> ColorComponents {
        let adapted = *from / Self::WHITE_POINT.values();

        // 4. Convert D50-adapted XYZ to Lab.
        let ColorComponents(f0, f1, f2) = adapted.map(|v| {
            if v > Self::EPSILON {
                v.cbrt()
            } else {
                (Self::KAPPA * v + 16.0) / 116.0
            }
        });

        let lightness = 116.0 * f1 - 16.0;
        let a = 500.0 * (f0 - f1);
        let b = 200.0 * (f1 - f2);

        ColorComponents(lightness, a, b)
    }

    fn to_gamma_encoded(from: &ColorComponents) -> ColorComponents {
        // No need for conversion.
        from.clone()
    }
}

/// The Lch color space.
/// https://drafts.csswg.org/css-color-4/#specifying-lab-lch
pub struct Lch;

impl ColorSpaceConversion for Lch {
    const WHITE_POINT: WhitePoint = Lab::WHITE_POINT;

    fn to_linear_light(from: &ColorComponents) -> ColorComponents {
        // No need for conversion.
        from.clone()
    }

    fn to_xyz(from: &ColorComponents) -> ColorComponents {
        // Convert LCH to Lab first.
        let lab = polar_to_orthogonal(from);

        // Then convert the Lab to XYZ.
        Lab::to_xyz(&lab)
    }

    fn from_xyz(from: &ColorComponents) -> ColorComponents {
        // First convert the XYZ to LAB.
        let lab = Lab::from_xyz(&from);

        // Then convert the Lab to LCH.
        orthogonal_to_polar(&lab, epsilon_for_range(0.0, 100.0))
    }

    fn to_gamma_encoded(from: &ColorComponents) -> ColorComponents {
        // No need for conversion.
        from.clone()
    }
}

/// The Oklab color space.
/// https://drafts.csswg.org/css-color-4/#specifying-oklab-oklch
pub struct Oklab;

impl Oklab {
    #[rustfmt::skip]
    const XYZ_TO_LMS: Transform = Transform::new(
         0.8190224432164319,  0.0329836671980271,  0.048177199566046255, 0.0,
         0.3619062562801221,  0.9292868468965546,  0.26423952494422764,  0.0,
        -0.12887378261216414, 0.03614466816999844, 0.6335478258136937,   0.0,
         0.0,                 0.0,                 0.0,                  1.0,
    );

    #[rustfmt::skip]
    const LMS_TO_OKLAB: Transform = Transform::new(
         0.2104542553,  1.9779984951,  0.0259040371, 0.0,
         0.7936177850, -2.4285922050,  0.7827717662, 0.0,
        -0.0040720468,  0.4505937099, -0.8086757660, 0.0,
         0.0,           0.0,           0.0,          1.0,
    );

    #[rustfmt::skip]
    const LMS_TO_XYZ: Transform = Transform::new(
         1.2268798733741557,  -0.04057576262431372, -0.07637294974672142, 0.0,
        -0.5578149965554813,   1.1122868293970594,  -0.4214933239627914,  0.0,
         0.28139105017721583, -0.07171106666151701,  1.5869240244272418,  0.0,
         0.0,                  0.0,                  0.0,                 1.0,
    );

    #[rustfmt::skip]
    const OKLAB_TO_LMS: Transform = Transform::new(
        0.99999999845051981432,  1.0000000088817607767,    1.0000000546724109177,   0.0,
        0.39633779217376785678, -0.1055613423236563494,   -0.089484182094965759684, 0.0,
        0.21580375806075880339, -0.063854174771705903402, -1.2914855378640917399,   0.0,
        0.0,                     0.0,                      0.0,                     1.0,
    );
}

impl ColorSpaceConversion for Oklab {
    const WHITE_POINT: WhitePoint = WhitePoint::D65;

    fn to_linear_light(from: &ColorComponents) -> ColorComponents {
        // No need for conversion.
        from.clone()
    }

    fn to_xyz(from: &ColorComponents) -> ColorComponents {
        let lms = transform(&from, &Self::OKLAB_TO_LMS);
        let lms = lms.map(|v| v * v * v);
        transform(&lms, &Self::LMS_TO_XYZ)
    }

    fn from_xyz(from: &ColorComponents) -> ColorComponents {
        let lms = transform(&from, &Self::XYZ_TO_LMS);
        let lms = lms.map(|v| v.cbrt());
        transform(&lms, &Self::LMS_TO_OKLAB)
    }

    fn to_gamma_encoded(from: &ColorComponents) -> ColorComponents {
        // No need for conversion.
        from.clone()
    }
}

/// The Oklch color space.
/// https://drafts.csswg.org/css-color-4/#specifying-oklab-oklch
pub struct Oklch;

impl ColorSpaceConversion for Oklch {
    const WHITE_POINT: WhitePoint = Oklab::WHITE_POINT;

    fn to_linear_light(from: &ColorComponents) -> ColorComponents {
        // No need for conversion.
        from.clone()
    }

    fn to_xyz(from: &ColorComponents) -> ColorComponents {
        // First convert OkLCH to Oklab.
        let oklab = polar_to_orthogonal(from);

        // Then convert Oklab to XYZ.
        Oklab::to_xyz(&oklab)
    }

    fn from_xyz(from: &ColorComponents) -> ColorComponents {
        // First convert XYZ to Oklab.
        let lab = Oklab::from_xyz(&from);

        // Then convert Oklab to OkLCH.
        orthogonal_to_polar(&lab, epsilon_for_range(0.0, 1.0))
    }

    fn to_gamma_encoded(from: &ColorComponents) -> ColorComponents {
        // No need for conversion.
        from.clone()
    }
}

[ Dauer der Verarbeitung: 0.36 Sekunden  (vorverarbeitet)  ]

                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge