【W3学习】CSS Color Module Level 4(CSS 颜色模块)

这个规范描述了 CSS <color> 值,以及前景色和组不透明度的属性。

 

 

em { color:  lime; }   /* color keyword  */
em { color:  rgb(0 255 0); } /* RGB range 0-255   */
em { color:  rgb(0% 100% 0%); } /* RGB range 0%-100% */
em { color:  color(sRGB 0 1 0); } /* sRGB range 0.0-1.0 */

CSS颜色定义的各种写法:

The color functions available in Level 4 are

rgb() and its rgba() alias, which (like the hex color notation) specify sRGB colors directly by their red/green/blue/alpha chanels.

hsl() and its hsla() alias, which specify sRGB colors by hue, saturation, and lightness using the HSL cylindrical coordinate model.

hwb(), which specifies an sRGB color by hue, whiteness, and blackness using the HWB cylindrical coordinate model.

lab(), which specifies a CIELAB color by CIE Lightness and its a- and b-axis hue coordinates (red/green-ness, and yellow/blue-ness) using the CIE LAB rectangular coordinate model.

lch() , which specifies a CIELAB color by CIE Lightness, Chroma, and hue using the CIE LCH cylindrical coordinate model

oklab(), which specifies an Oklab color by Oklab Lightness and its a- and b-axis hue coordinates (red/green-ness, and yellow/blue-ness) using the Oklab rectangular coordinate model.

oklch() , which specifies an Oklab color by Oklab Lightness, Chroma, and hue using the Oklch cylindrical coordinate model.

color(), which allows specifying colors in a variety of color spaces including sRGB, Linear-light sRGB, Display P3, A98 RGB, ProPhoto RGB, ITU-R BT.2020-2, and CIE XYZ.

For easy reference in other specifications, opaque black is defined as the color rgb(0 0 0 / 100%); transparent black is the same color, but fully transparent—i.e. rgb(0 0 0 / 0%).

颜色转换代码Sample code for Color Conversions:

// Sample code for color conversions
// Conversion can also be done using ICC profiles and a Color Management System
// For clarity, a library is used for matrix multiplication (multiply-matrices.js)

// standard white points, defined by 4-figure CIE x,y chromaticities
const D50 = [0.3457 / 0.3585, 1.00000, (1.0 - 0.3457 - 0.3585) / 0.3585];
const D65 = [0.3127 / 0.3290, 1.00000, (1.0 - 0.3127 - 0.3290) / 0.3290];

// sRGB-related functions

function lin_sRGB(RGB) {
    // convert an array of sRGB values
    // where in-gamut values are in the range [0 - 1]
    // to linear light (un-companded) form.
    // https://en.wikipedia.org/wiki/SRGB
    // Extended transfer function:
    // for negative values,  linear portion is extended on reflection of axis,
    // then reflected power function is used.
    return RGB.map(function (val) {
        let sign = val < 0? -1 : 1;
        let abs = Math.abs(val);

        if (abs < 0.04045) {
            return val / 12.92;
        }

        return sign * (Math.pow((abs + 0.055) / 1.055, 2.4));
    });
}

function gam_sRGB(RGB) {
    // convert an array of linear-light sRGB values in the range 0.0-1.0
    // to gamma corrected form
    // https://en.wikipedia.org/wiki/SRGB
    // Extended transfer function:
    // For negative values, linear portion extends on reflection
    // of axis, then uses reflected pow below that
    return RGB.map(function (val) {
        let sign = val < 0? -1 : 1;
        let abs = Math.abs(val);

        if (abs > 0.0031308) {
            return sign * (1.055 * Math.pow(abs, 1/2.4) - 0.055);
        }

        return 12.92 * val;
    });
}

function lin_sRGB_to_XYZ(rgb) {
    // convert an array of linear-light sRGB values to CIE XYZ
    // using sRGB's own white, D65 (no chromatic adaptation)

    var M = [
        [ 506752 / 1228815,  87881 / 245763,   12673 /   70218 ],
        [  87098 /  409605, 175762 / 245763,   12673 /  175545 ],
        [   7918 /  409605,  87881 / 737289, 1001167 / 1053270 ],
    ];
    return multiplyMatrices(M, rgb);
}

function XYZ_to_lin_sRGB(XYZ) {
    // convert XYZ to linear-light sRGB

    var M = [
        [   12831 /   3959,    -329 /    214, -1974 /   3959 ],
        [ -851781 / 878810, 1648619 / 878810, 36519 / 878810 ],
        [     705 /  12673,   -2585 /  12673,   705 /    667 ],
    ];

    return multiplyMatrices(M, XYZ);
}

//  display-p3-related functions


function lin_P3(RGB) {
    // convert an array of display-p3 RGB values in the range 0.0 - 1.0
    // to linear light (un-companded) form.

    return lin_sRGB(RGB);    // same as sRGB
}

function gam_P3(RGB) {
    // convert an array of linear-light display-p3 RGB  in the range 0.0-1.0
    // to gamma corrected form

    return gam_sRGB(RGB);    // same as sRGB
}

function lin_P3_to_XYZ(rgb) {
    // convert an array of linear-light display-p3 values to CIE XYZ
    // using  D65 (no chromatic adaptation)
    // http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html
    var M = [
        [ 608311 / 1250200, 189793 / 714400,  198249 / 1000160 ],
        [  35783 /  156275, 247089 / 357200,  198249 / 2500400 ],
        [      0 /       1,  32229 / 714400, 5220557 / 5000800 ],
    ];

    return multiplyMatrices(M, rgb);
}

function XYZ_to_lin_P3(XYZ) {
    // convert XYZ to linear-light P3
    var M = [
        [ 446124 / 178915, -333277 / 357830, -72051 / 178915 ],
        [ -14852 /  17905,   63121 /  35810,    423 /  17905 ],
        [  11844 / 330415,  -50337 / 660830, 316169 / 330415 ],
    ];

    return multiplyMatrices(M, XYZ);
}

// prophoto-rgb functions

function lin_ProPhoto(RGB) {
    // convert an array of prophoto-rgb values
    // where in-gamut colors are in the range [0.0 - 1.0]
    // to linear light (un-companded) form.
    // Transfer curve is gamma 1.8 with a small linear portion
    // Extended transfer function
    const Et2 = 16/512;
    return RGB.map(function (val) {
        let sign = val < 0? -1 : 1;
        let abs = Math.abs(val);

        if (abs <= Et2) {
            return val / 16;
        }

        return sign * Math.pow(abs, 1.8);
    });
}

function gam_ProPhoto(RGB) {
    // convert an array of linear-light prophoto-rgb  in the range 0.0-1.0
    // to gamma corrected form
    // Transfer curve is gamma 1.8 with a small linear portion
    // TODO for negative values, extend linear portion on reflection of axis, then add pow below that
    const Et = 1/512;
    return RGB.map(function (val) {
        let sign = val < 0? -1 : 1;
        let abs = Math.abs(val);

        if (abs >= Et) {
            return sign * Math.pow(abs, 1/1.8);
        }

        return 16 * val;
    });
}

function lin_ProPhoto_to_XYZ(rgb) {
    // convert an array of linear-light prophoto-rgb values to CIE XYZ
    // using  D50 (so no chromatic adaptation needed afterwards)
    // http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html
    var M = [
        [ 0.7977604896723027,  0.13518583717574031,  0.0313493495815248     ],
        [ 0.2880711282292934,  0.7118432178101014,   0.00008565396060525902 ],
        [ 0.0,                 0.0,                  0.8251046025104601     ]
    ];

    return multiplyMatrices(M, rgb);
}

function XYZ_to_lin_ProPhoto(XYZ) {
    // convert XYZ to linear-light prophoto-rgb
    var M = [
          [  1.3457989731028281,  -0.25558010007997534,  -0.05110628506753401 ],
          [ -0.5446224939028347,   1.5082327413132781,    0.02053603239147973 ],
          [  0.0,                  0.0,                   1.2119675456389454  ]
    ];

    return multiplyMatrices(M, XYZ);
}

// a98-rgb functions

function lin_a98rgb(RGB) {
    // convert an array of a98-rgb values in the range 0.0 - 1.0
    // to linear light (un-companded) form.
    // negative values are also now accepted
    return RGB.map(function (val) {
        let sign = val < 0? -1 : 1;
        let abs = Math.abs(val);

          return sign * Math.pow(abs, 563/256);
    });
}

function gam_a98rgb(RGB) {
    // convert an array of linear-light a98-rgb  in the range 0.0-1.0
    // to gamma corrected form
    // negative values are also now accepted
    return RGB.map(function (val) {
        let sign = val < 0? -1 : 1;
        let abs = Math.abs(val);

        return sign * Math.pow(abs, 256/563);
    });
}

function lin_a98rgb_to_XYZ(rgb) {
    // convert an array of linear-light a98-rgb values to CIE XYZ
    // http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html
    // has greater numerical precision than section 4.3.5.3 of
    // https://www.adobe.com/digitalimag/pdfs/AdobeRGB1998.pdf
    // but the values below were calculated from first principles
    // from the chromaticity coordinates of R G B W
    // see matrixmaker.html
    var M = [
        [ 573536 /  994567,  263643 / 1420810,  187206 /  994567 ],
        [ 591459 / 1989134, 6239551 / 9945670,  374412 / 4972835 ],
        [  53769 / 1989134,  351524 / 4972835, 4929758 / 4972835 ],
    ];

    return multiplyMatrices(M, rgb);
}

function XYZ_to_lin_a98rgb(XYZ) {
    // convert XYZ to linear-light a98-rgb
    var M = [
        [ 1829569 /  896150, -506331 /  896150, -308931 /  896150 ],
        [ -851781 /  878810, 1648619 /  878810,   36519 /  878810 ],
        [   16779 / 1248040, -147721 / 1248040, 1266979 / 1248040 ],
    ];

    return multiplyMatrices(M, XYZ);
}

//Rec. 2020-related functions

function lin_2020(RGB) {
    // convert an array of rec2020 RGB values in the range 0.0 - 1.0
    // to linear light (un-companded) form.
    // ITU-R BT.2020-2 p.4

    const α = 1.09929682680944 ;
    const β = 0.018053968510807;

    return RGB.map(function (val) {
        let sign = val < 0? -1 : 1;
        let abs = Math.abs(val);

        if (abs < β * 4.5 ) {
            return val / 4.5;
        }

        return sign * (Math.pow((abs + α -1 ) / α, 1/0.45));
    });
}

function gam_2020(RGB) {
    // convert an array of linear-light rec2020 RGB  in the range 0.0-1.0
    // to gamma corrected form
    // ITU-R BT.2020-2 p.4

    const α = 1.09929682680944 ;
    const β = 0.018053968510807;


    return RGB.map(function (val) {
        let sign = val < 0? -1 : 1;
        let abs = Math.abs(val);

        if (abs > β ) {
            return sign * (α * Math.pow(abs, 0.45) - (α - 1));
        }

        return 4.5 * val;
    });
}

function lin_2020_to_XYZ(rgb) {
    // convert an array of linear-light rec2020 values to CIE XYZ
    // using  D65 (no chromatic adaptation)
    // http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html
    var M = [
        [ 63426534 / 99577255,  20160776 / 139408157,  47086771 / 278816314 ],
        [ 26158966 / 99577255, 472592308 / 697040785,   8267143 / 139408157 ],
        [        0 /        1,  19567812 / 697040785, 295819943 / 278816314 ],
    ];
    // 0 is actually calculated as  4.994106574466076e-17

    return multiplyMatrices(M, rgb);
}

function XYZ_to_lin_2020(XYZ) {
    // convert XYZ to linear-light rec2020
    var M = [
        [  30757411 / 17917100, -6372589 / 17917100, -4539589 / 17917100 ],
        [ -19765991 / 29648200, 47925759 / 29648200,   467509 / 29648200 ],
        [    792561 / 44930125, -1921689 / 44930125, 42328811 / 44930125 ],
    ];

    return multiplyMatrices(M, XYZ);
}

// Chromatic adaptation

function D65_to_D50(XYZ) {
    // Bradford chromatic adaptation from D65 to D50
    // The matrix below is the result of three operations:
    // - convert from XYZ to retinal cone domain
    // - scale components from one reference white to another
    // - convert back to XYZ
    // http://www.brucelindbloom.com/index.html?Eqn_ChromAdapt.html
    var M =  [
        [  1.0479298208405488,    0.022946793341019088,  -0.05019222954313557 ],
        [  0.029627815688159344,  0.990434484573249,     -0.01707382502938514 ],
        [ -0.009243058152591178,  0.015055144896577895,   0.7518742899580008  ]
    ];

    return multiplyMatrices(M, XYZ);
}

function D50_to_D65(XYZ) {
    // Bradford chromatic adaptation from D50 to D65
    var M = [
        [  0.9554734527042182,   -0.023098536874261423,  0.0632593086610217   ],
        [ -0.028369706963208136,  1.0099954580058226,    0.021041398966943008 ],
        [  0.012314001688319899, -0.020507696433477912,  1.3303659366080753   ]
    ];

    return multiplyMatrices(M, XYZ);
}

// CIE Lab and LCH

function XYZ_to_Lab(XYZ) {
    // Assuming XYZ is relative to D50, convert to CIE Lab
    // from CIE standard, which now defines these as a rational fraction
    var ε = 216/24389;  // 6^3/29^3
    var κ = 24389/27;   // 29^3/3^3

    // compute xyz, which is XYZ scaled relative to reference white
    var xyz = XYZ.map((value, i) => value / D50[i]);

    // now compute f
    var f = xyz.map(value => value > ε ? Math.cbrt(value) : (κ * value + 16)/116);

    return [
        (116 * f[1]) - 16,      // L
        500 * (f[0] - f[1]), // a
        200 * (f[1] - f[2])  // b
    ];
    // L in range [0,100]. For use in CSS, add a percent
}

function Lab_to_XYZ(Lab) {
    // Convert Lab to D50-adapted XYZ
    // http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html
    var κ = 24389/27;   // 29^3/3^3
    var ε = 216/24389;  // 6^3/29^3
    var f = [];

    // compute f, starting with the luminance-related term
    f[1] = (Lab[0] + 16)/116;
    f[0] = Lab[1]/500 + f[1];
    f[2] = f[1] - Lab[2]/200;

    // compute xyz
    var xyz = [
        Math.pow(f[0],3) > ε ?   Math.pow(f[0],3)            : (116*f[0]-16)/κ,
        Lab[0] > κ * ε ?         Math.pow((Lab[0]+16)/116,3) : Lab[0]/κ,
        Math.pow(f[2],3)  > ε ?  Math.pow(f[2],3)            : (116*f[2]-16)/κ
    ];

    // Compute XYZ by scaling xyz by reference white
    return xyz.map((value, i) => value * D50[i]);
}

function Lab_to_LCH(Lab) {
    // Convert to polar form
    var hue = Math.atan2(Lab[2], Lab[1]) * 180 / Math.PI;
    return [
        Lab[0], // L is still L
        Math.sqrt(Math.pow(Lab[1], 2) + Math.pow(Lab[2], 2)), // Chroma
        hue >= 0 ? hue : hue + 360 // Hue, in degrees [0 to 360)
    ];
}

function LCH_to_Lab(LCH) {
    // Convert from polar form
    return [
        LCH[0], // L is still L
        LCH[1] * Math.cos(LCH[2] * Math.PI / 180), // a
        LCH[1] * Math.sin(LCH[2] * Math.PI / 180) // b
    ];
}

// OKLab and OKLCH
// https://bottosson.github.io/posts/oklab/

// XYZ <-> LMS matrices recalculated for consistent reference white
// see https://github.com/w3c/csswg-drafts/issues/6642#issuecomment-943521484

function XYZ_to_OKLab(XYZ) {
    // Given XYZ relative to D65, convert to OKLab
    var XYZtoLMS = [
        [ 0.8190224432164319,    0.3619062562801221,   -0.12887378261216414  ],
        [ 0.0329836671980271,    0.9292868468965546,     0.03614466816999844 ],
        [ 0.048177199566046255,  0.26423952494422764,    0.6335478258136937  ]
    ];
    var LMStoOKLab = [
        [  0.2104542553,   0.7936177850,  -0.0040720468 ],
        [  1.9779984951,  -2.4285922050,   0.4505937099 ],
        [  0.0259040371,   0.7827717662,  -0.8086757660 ]
    ];

    var LMS = multiplyMatrices(XYZtoLMS, XYZ);
    return multiplyMatrices(LMStoOKLab, LMS.map(c => Math.cbrt(c)));
    // L in range [0,1]. For use in CSS, multiply by 100 and add a percent
}

function OKLab_to_XYZ(OKLab) {
    // Given OKLab, convert to XYZ relative to D65
    var LMStoXYZ =  [
        [  1.2268798733741557,  -0.5578149965554813,   0.28139105017721583 ],
        [ -0.04057576262431372,  1.1122868293970594,  -0.07171106666151701 ],
        [ -0.07637294974672142, -0.4214933239627914,   1.5869240244272418  ]
    ];
    var OKLabtoLMS = [
        [ 0.99999999845051981432,  0.39633779217376785678,   0.21580375806075880339  ],
        [ 1.0000000088817607767,  -0.1055613423236563494,   -0.063854174771705903402 ],
        [ 1.0000000546724109177,  -0.089484182094965759684, -1.2914855378640917399   ]
    ];

    var LMSnl = multiplyMatrices(OKLabtoLMS, OKLab);
    return multiplyMatrices(LMStoXYZ, LMSnl.map(c => c ** 3));
}

function OKLab_to_OKLCH(OKLab) {
    var hue = Math.atan2(OKLab[2], OKLab[1]) * 180 / Math.PI;
    return [
        OKLab[0], // L is still L
        Math.sqrt(OKLab[1] ** 2 + OKLab[2] ** 2), // Chroma
        hue >= 0 ? hue : hue + 360 // Hue, in degrees [0 to 360)
    ];
}

function OKLCH_to_OKLab(OKLCH) {
    return [
        OKLCH[0], // L is still L
        OKLCH[1] * Math.cos(OKLCH[2] * Math.PI / 180), // a
        OKLCH[1] * Math.sin(OKLCH[2] * Math.PI / 180)  // b
    ];
}

// Premultiplied alpha conversions

function rectangular_premultiply(color, alpha) {
// given a color in a rectangular orthogonal colorspace
// and an alpha value
// return the premultiplied form
    return color.map((c) => c * alpha)
}

function rectangular_un_premultiply(color, alpha) {
// given a premultiplied color in a rectangular orthogonal colorspace
// and an alpha value
// return the actual color
    if (alpha === 0) {
        return color; // avoid divide by zero
    }
    return color.map((c) => c / alpha)
}

function polar_premultiply(color, alpha, hueIndex) {
    // given a color in a cylindicalpolar colorspace
    // and an alpha value
    // return the premultiplied form.
    // the index says which entry in the color array corresponds to hue angle
    // for example, in OKLCH it would be 2
    // while in HSL it would be 0
    return color.map((c, i) => c * (hueIndex === i? 1 : alpha))
}

function polar_un_premultiply(color, alpha, hueIndex) {
    // given a color in a cylindicalpolar colorspace
    // and an alpha value
    // return the actual color.
    // the hueIndex says which entry in the color array corresponds to hue angle
    // for example, in OKLCH it would be 2
    // while in HSL it would be 0
    if (alpha === 0) {
        return color; // avoid divide by zero
    }
    return color.map((c, i) => c / (hueIndex === i? 1 : alpha))
}

// Convenience functions can easily be defined, such as
function hsl_premultiply(color, alpha) {
    return polar_premultiply(color, alpha, 0);
}

 

posted on 2022-11-09 10:58  facenano  阅读(166)  评论(0编辑  收藏  举报