@@ -2447,6 +2447,10 @@ Guaranteeing Adequate Contrast: the ''contrast'' adjuster</h3>
24472447 <p>
24482448 To compute the <dfn>luminance</dfn> of a color:
24492449
2450+ <p class="issue"> The code below is only for sRGB, and duplicates the
2451+ more general code in the conversions appendix.
2452+ </p>
2453+
24502454 <ol class="lang-javascript">
24512455 <li>
24522456 Scale the red, green, and blue channels of the color to the range [0,1] .
@@ -4269,7 +4273,260 @@ The CSSColor Object</h3>
42694273
42704274 </dl>
42714275
4276+ <h2 id="color-conversion-code">Sample code for color conversions</h2>
4277+
4278+ <pre class="lang-javascript">
4279+ <!-- imported 11 May 2016-->
4280+ // sRGB-related functions
4281+
4282+ function lin_sRGB(RGB) {
4283+ // convert an array of sRGB values in the range 0.0 - 1.0
4284+ // to linear light (un-companded) form.
4285+ // https://en.wikipedia.org/wiki/SRGB
4286+ return RGB.map(function (val) {
4287+ if (val < 0.04045) {
4288+ return val / 12.92;
4289+ }
4290+
4291+ return Math.pow((val + 0.055) / 1.055, 2.4);
4292+ });
4293+ }
4294+
4295+ function gam_sRGB(RGB) {
4296+ // convert an array of linear-light sRGB values in the range 0.0-1.0
4297+ // to gamma corrected form
4298+ // https://en.wikipedia.org/wiki/SRGB
4299+ return RGB.map(function (val) {
4300+ if (val > 0.0031308) {
4301+ return 1.055 * Math.pow(val, 1/2.4) - 0.055;
4302+ }
4303+
4304+ return 12.92 * val;
4305+ });
4306+ }
4307+
4308+ function lin_sRGB_to_XYZ(rgb) {
4309+ // convert an array of linear-light sRGB values to CIE XYZ
4310+ // using sRGB's own white, D65 (no chromatic adaptation)
4311+ // http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html
4312+ var M = math.matrix([
4313+ [0.4124564, 0.3575761, 0.1804375] ,
4314+ [0.2126729, 0.7151522, 0.0721750] ,
4315+ [0.0193339, 0.1191920, 0.9503041]
4316+ ]);
4317+
4318+ return math.multiply(M, rgb).valueOf();
4319+ }
4320+
4321+ function XYZ_to_lin_sRGB(XYZ) {
4322+ // convert XYZ to linear-light sRGB
4323+ var M = math.matrix([
4324+ [ 3.2404542, -1.5371385, -0.4985314] ,
4325+ [-0.9692660, 1.8760108, 0.0415560] ,
4326+ [ 0.0556434, -0.2040259, 1.0572252]
4327+ ]);
4328+
4329+ return math.multiply(M, XYZ).valueOf();
4330+ }
4331+
4332+ // DCI P3-related functions
4333+
42724334
4335+ function lin_P3(RGB) {
4336+ // convert an array of DCI P3 RGB values in the range 0.0 - 1.0
4337+ // to linear light (un-companded) form.
4338+
4339+ return RGB.map(function (val) {
4340+ return Math.pow(val, 2.6);
4341+ });
4342+ }
4343+
4344+ function gam_P3(RGB) {
4345+ // convert an array of linear-light P3 RGB in the range 0.0-1.0
4346+ // to gamma corrected form
4347+
4348+ return RGB.map(function (val) {
4349+ return Math.pow(val, 1/2.6);
4350+ }
4351+ });
4352+ }
4353+
4354+ function lin_P3_to_XYZ(rgb) {
4355+ // convert an array of linear-light P3 values to CIE XYZ
4356+ // using D65 (no chromatic adaptation)
4357+ // http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html
4358+ var M = math.matrix([
4359+ [0.4865709486482162, 0.26566769316909306, 0.1982172852343625] ,
4360+ [0.2289745640697488, 0.6917385218365064, 0.079286914093745] ,
4361+ [0.0000000000000000, 0.04511338185890264, 1.043944368900976]
4362+ ]);
4363+ // 0 was computed as -3.972075516933488e-17
4364+
4365+ return math.multiply(M, rgb).valueOf();
4366+ }
4367+
4368+ function XYZ_to_lin_P3(XYZ) {
4369+ // convert XYZ to linear-light P3
4370+ var M = math.matrix([
4371+ [ 2.493496911941425, -0.9313836179191239, -0.40271078445071684] ,
4372+ [-0.8294889695615747, 1.7626640603183463, 0.023624685841943577] ,
4373+ [ 0.03584583024378447, -0.07617238926804182, 0.9568845240076872]
4374+ ]);
4375+
4376+ return math.multiply(M, XYZ).valueOf();
4377+ }
4378+
4379+ //Rec.2020-related functions
4380+
4381+ function lin_2020(RGB) {
4382+ // convert an array of Rec.2020 RGB values in the range 0.0 - 1.0
4383+ // to linear light (un-companded) form.
4384+ const α = 1.09929682680944 ;
4385+ const β = 0.018053968510807;
4386+
4387+ return RGB.map(function (val) {
4388+ if (val < β * 4.5 ) {
4389+ return val / 4.5;
4390+ }
4391+
4392+ return Math.pow((val + α -1 ) / α, 2.4);
4393+ });
4394+ }
4395+ //check with standard this really is 2.4 and 1/2.4, not 0.45 was wikipedia claims
4396+
4397+ function gam_2020(RGB) {
4398+ // convert an array of linear-light Rec.2020 RGB in the range 0.0-1.0
4399+ // to gamma corrected form
4400+ const α = 1.09929682680944 ;
4401+ const β = 0.018053968510807;
4402+
4403+ return RGB.map(function (val) {
4404+ if (val > β ) {
4405+ return α * Math.pow(val, 1/2.4) - (α - 1);
4406+ }
4407+
4408+ return 4.5 * val;
4409+ });
4410+ }
4411+
4412+ function lin_2020_to_XYZ(rgb) {
4413+ // convert an array of linear-light rec.2020 values to CIE XYZ
4414+ // using D65 (no chromatic adaptation)
4415+ // http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html
4416+ var M = math.matrix([
4417+ [0.6369580483012914, 0.14461690358620832, 0.1688809751641721] ,
4418+ [0.2627002120112671, 0.6779980715188708, 0.05930171646986196] ,
4419+ [0.000000000000000, 0.028072693049087428, 1.060985057710791]
4420+ ]);
4421+ // 0 is actually calculated as 4.994106574466076e-17
4422+
4423+ return math.multiply(M, rgb).valueOf();
4424+ }
4425+
4426+ function XYZ_to_lin_2020(XYZ) {
4427+ // convert XYZ to linear-light rec.2020
4428+ var M = math.matrix([
4429+ [1.7166511879712674, -0.35567078377639233, -0.25336628137365974] ,
4430+ [-0.6666843518324892, 1.6164812366349395, 0.01576854581391113] ,
4431+ [0.017639857445310783, -0.042770613257808524, 0.9421031212354738]
4432+ ]);
4433+
4434+ return math.multiply(M, XYZ).valueOf();
4435+ }
4436+
4437+ // Chromatic adaptation
4438+
4439+ function D65_to_D50(XYZ) {
4440+ // Bradford chromatic adaptation from D65 to D50
4441+ // The matrix below is the result of three operations:
4442+ // - convert from XYZ to retinal cone domain
4443+ // - scale components from one reference white to another
4444+ // - convert back to XYZ
4445+ // http://www.brucelindbloom.com/index.html?Eqn_ChromAdapt.html
4446+ var M = math.matrix([
4447+ [ 1.0478112, 0.0228866, -0.0501270] ,
4448+ [ 0.0295424, 0.9904844, -0.0170491] ,
4449+ [-0.0092345, 0.0150436, 0.7521316]
4450+ ]);
4451+
4452+ return math.multiply(M, XYZ).valueOf();
4453+ }
4454+
4455+ function D50_to_D65(XYZ) {
4456+ // Bradford chromatic adaptation from D50 to D65
4457+ var M = math.matrix([
4458+ [ 0.9555766, -0.0230393, 0.0631636] ,
4459+ [-0.0282895, 1.0099416, 0.0210077] ,
4460+ [ 0.0122982, -0.0204830, 1.3299098]
4461+ ]);
4462+
4463+ return math.multiply(M, XYZ).valueOf();
4464+ }
4465+
4466+ // Lab and LCH
4467+
4468+ function XYZ_to_Lab(XYZ) {
4469+ // Assuming XYZ is relative to D50, convert to CIE Lab
4470+ // from CIE standard, which now defines these as a rational fraction
4471+ var ε = 216/24389; // 6^3/29^3
4472+ var κ = 24389/27; // 29^3/3^3
4473+ var white = [0.9642, 1.0000, 0.8249] ; // D50 reference white
4474+
4475+ // compute xyz, which is XYZ scaled relative to reference white
4476+ var xyz = XYZ.map((value, i) => value / white[i] );
4477+
4478+ // now compute f
4479+ var f = xyz.map(value => value > ε ? Math.cbrt(value) : (κ * value + 16)/116);
4480+
4481+ return [
4482+ (116 * f[1] ) - 16, // L
4483+ 500 * (f[0] - f[1] ), // a
4484+ 200 * (f[1] - f[2] ) // b
4485+ ];
4486+ }
4487+
4488+ function Lab_to_XYZ(Lab) {
4489+ // Convert Lab to D50-adapted XYZ
4490+ var κ = 24389/27; // 29^3/3^3
4491+ var ε = 216/24389; // 6^3/29^3
4492+ var white = [0.9642, 1.0000, 0.8249] ; // D50 reference white
4493+ var f = [];
4494+
4495+ // compute f, starting with the luminance-related term
4496+ f[1] = (Lab[0] + 16)/116;
4497+ f[0] = Lab[1] /500 + f[1] ;
4498+ f[2] = f[1] - Lab[2] /200;
4499+
4500+ // compute xyz
4501+ var xyz = [
4502+ f[0] > ε ? Math.pow(f[0] ,3) : (116*f[0] -16)/κ,
4503+ Lab[0] > κ * ε ? Math.pow((Lab[0] +16)/116,3) : Lab[0] /κ,
4504+ f[2] > ε ? Math.pow(f[2] ,3) : (116*f[2] -16)/κ
4505+ ];
4506+
4507+ // Compute XYZ by scaling xyz by reference white
4508+ return xyz.map((value, i) => value * white[i] );
4509+ }
4510+
4511+ function Lab_to_LCH(Lab) {
4512+ // Convert to polar form
4513+ return [
4514+ Lab[0] , // L is still L
4515+ Math.sqrt(Math.pow(Lab[1] , 2) + Math.pow(Lab[2] , 2)), // Chroma
4516+ Math.atan2(Lab[2] , Lab[1] ) * 180 / Math.PI // Hue, in degrees
4517+ ];
4518+ }
4519+
4520+ function LCH_to_Lab(LCH) {
4521+ // Convert from polar form
4522+ return [
4523+ LCH[0] , // L is still L
4524+ LCH[1] * Math.cos(LCH[2] * Math.PI / 180), // a
4525+ LCH[1] * Math.sin(LCH[2] * Math.PI / 180) // b
4526+ ];
4527+ }
4528+
4529+ </pre>
42734530
42744531<h2 id="system-colors" class="no-num">
42754532Appendix A: Deprecated CSS System Colors</h2>
0 commit comments