Skip to content

Commit bc3af3a

Browse files
committed
[css-color-hdr] Added sample code for rec2100 colorspaces
1 parent 4d297e1 commit bc3af3a

File tree

5 files changed

+173
-19
lines changed

5 files changed

+173
-19
lines changed

css-color-hdr-1/ICtCp.js

+19-19
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11

22
function XYZ_to_ICtCp (XYZ) {
3-
// convert an array of absolute, D65 XYZ to the ICtCp form of LMS
3+
// convert an array of absolute, D65 XYZ to the ICtCp form of LMS
44

55
// The matrix below includes the 4% crosstalk components
66
// and is from the procedure in the Dolby "What is ICtCp" paper"
@@ -32,17 +32,17 @@ function LMStoICtCp (LMS) {
3232
[ 17933 / 4096, -17390 / 4096, -543 / 4096 ],
3333
];
3434

35-
// apply the PQ EOTF
36-
// we can't ever be dividing by zero because of the "1 +" in the denominator
37-
let PQLMS = LMS.map (function (val) {
38-
let num = c1 + (c2 * ((val / 10000) ** m1));
39-
let denom = 1 + (c3 * ((val / 10000) ** m1));
35+
// apply the PQ EOTF
36+
// we can't ever be dividing by zero because of the "1 +" in the denominator
37+
let PQLMS = LMS.map (function (val) {
38+
let num = c1 + (c2 * ((val / 10000) ** m1));
39+
let denom = 1 + (c3 * ((val / 10000) ** m1));
4040

41-
return (num / denom) ** m2;
42-
});
41+
return (num / denom) ** m2;
42+
});
4343

44-
// LMS to IPT, with rotation for Y'C'bC'r compatibility
45-
return multiplyMatrices(M, PQLMS);
44+
// LMS to IPT, with rotation for Y'C'bC'r compatibility
45+
return multiplyMatrices(M, PQLMS);
4646
}
4747

4848
function ICtCp_to_XYZ (ICtCp) {
@@ -55,7 +55,7 @@ function ICtCp_to_XYZ (ICtCp) {
5555
];
5656

5757
let LMS = ICtCptoLMS(ICtCp);
58-
return multiplyMatrices(M, LMS);
58+
return multiplyMatrices(M, LMS);
5959
}
6060

6161
function ICtCptoLMS (ICtCp) {
@@ -72,14 +72,14 @@ function ICtCptoLMS (ICtCp) {
7272
[ 0.9999999999999998, 0.5600313357106791, -0.3206271749873188 ],
7373
];
7474

75-
let PQLMS = multiplyMatrices(M, ICtCp);
75+
let PQLMS = multiplyMatrices(M, ICtCp);
7676

77-
// Undo PQ encoding, From BT.2124-0 Annex 2 Conversion 3
78-
let LMS = PQLMS.map (function (val) {
79-
let num = Math.max((val ** im2) - c1, 0);
80-
let denom = (c2 - (c3 * (val ** im2)));
81-
return 10000 * ((num / denom) ** im1);
82-
});
77+
// Undo PQ encoding, From BT.2124-0 Annex 2 Conversion 3
78+
let LMS = PQLMS.map (function (val) {
79+
let num = Math.max((val ** im2) - c1, 0);
80+
let denom = (c2 - (c3 * (val ** im2)));
81+
return 10000 * ((num / denom) ** im1);
82+
});
8383

84-
return LMS;
84+
return LMS;
8585
}

css-color-hdr-1/Overview.bs

+31
Original file line numberDiff line numberDiff line change
@@ -1074,6 +1074,37 @@ Serializing values of the ''color()'' function</h3>
10741074
as the ones used in ''ICtCp'',
10751075
take care to use the right ones!
10761076

1077+
<h3 id="rec2100-linear_code">
1078+
Sample code for ''rec2100-linear''
1079+
</h3>
1080+
1081+
The BT.2020 and BT.2100 color spaces
1082+
use the same RGB primaries and white point,
1083+
and both place media white at a component value of 1.0.
1084+
1085+
<pre class="include-code lang-javascript">
1086+
path: rec2100-linear.js
1087+
highlight: js
1088+
</pre>
1089+
1090+
<h3 id="rec2100-pq_code">
1091+
Sample code for ''rec2100-pq''
1092+
</h3>
1093+
1094+
<pre class="include-code lang-javascript">
1095+
path: rec2100-pq.js
1096+
highlight: js
1097+
</pre>
1098+
1099+
<h3 id="rec2100-pq_code">
1100+
Sample code for ''rec2100-hlg''
1101+
</h3>
1102+
1103+
<pre class="include-code lang-javascript">
1104+
path: rec2100-hlg.js
1105+
highlight: js
1106+
</pre>
1107+
10771108
<h3 id="ictcp_code">
10781109
Sample code for ''ICtCp''
10791110
</h3>

css-color-hdr-1/rec2100-hlg.js

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
function XYZ_to_hlg_2100(XYZ) {
2+
// convert an array of D65 XYZ to HLG-encoded BT.2100 RGB
3+
// such that [0,0,0] is black and [0.75,0.75,0.75] is media white
4+
5+
let linRGB = XYZ_to_lin_2100(XYZ);
6+
return hlg_encode(linRGB);
7+
}
8+
9+
function hlg_2100_to_XYZ(RGB) {
10+
// convert an array of PQ-encoded BT.2100 RGB values
11+
// to D65 XYZ
12+
13+
let linRGB = hlg_decode(RGB);
14+
return lin_2100_to_XYZ(linRGB);
15+
}
16+
17+
function hlg_encode(RGB) {
18+
19+
const a = 0.17883277;
20+
const b = 0.28466892; // 1 - (4 * a)
21+
const c = 0.55991073; // 0.5 - a * Math.log(4 *a)
22+
const scale = 3.7743; // Place 18% grey at HLG 0.38, so media white at 0.75
23+
24+
return RGB.map(function (val) {
25+
// first scale to put linear-light media white at 1/3
26+
val /= scale;
27+
// now the HLG OETF
28+
// ITU-R BT.2390-10 p.23
29+
// 6.1 The hybrid log-gamma opto-electronic transfer function (OETF)
30+
if (val <= 1 / 12) {
31+
return spow(3 * val, 0.5);
32+
}
33+
return a * Math.log(12 * val - b) + c;
34+
});
35+
}
36+
37+
function hlg_decode(RGB) {
38+
39+
const a = 0.17883277;
40+
const b = 0.28466892; // 1 - (4 * a)
41+
const c = 0.55991073; // 0.5 - a * Math.log(4 *a)
42+
const scale = 3.7743; // Place 18% grey at HLG 0.38, so media white at 0.75
43+
44+
return RGB.map(function (val) {
45+
// first the HLG EOTF
46+
// ITU-R BT.2390-10 p.30 section
47+
// 6.3 The hybrid log-gamma electro-optical transfer function (EOTF)
48+
// Then scale by 3 so media white is 1.0
49+
if (val <= 0.5) {
50+
return (val ** 2) / 3 * scale;
51+
}
52+
return ((Math.exp((val - c) / a) + b) / 12) * scale;
53+
});
54+
}

css-color-hdr-1/rec2100-linear.js

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// These functions use the color conversion functions from CSS Color 4
2+
3+
function XYZ_to_lin_2100(XYZ) {
4+
// convert an array of D65 XYZ to linear-light BT.2100 RGB
5+
// such that [0,0,0] is black and [1,1,1] is media white.
6+
// component values greater than 1 indicate HDR colors
7+
8+
return XYZ_to_lin_2020(XYZ);
9+
}
10+
11+
function lin_2100_to_XYZ(RGB) {
12+
// convert an array of linear-light BT.2100 RGB values
13+
// to D65 XYZ
14+
15+
return lin_2020_to_XYZ(RGB);
16+
}

css-color-hdr-1/rec2100-pq.js

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
function XYZ_to_pq_2100(XYZ) {
2+
// convert an array of D65 XYZ to PQ-encoded BT.2100 RGB
3+
// such that [0,0,0] is black and [1,1,1] is 10,000 cd/m^2 white;
4+
// media white is at [0.5807,0.5807,0.5807] (to four significant figures).
5+
6+
let linRGB = XYZ_to_lin_2100(XYZ);
7+
return pq_encode(linRGB);
8+
}
9+
10+
function pq_2100_to_XYZ(RGB) {
11+
// convert an array of PQ-encoded BT.2100 RGB values
12+
// to D65 XYZ
13+
14+
let linRGB = pq_decode(RGB);
15+
return lin_2100_to_XYZ(linRGB);
16+
}
17+
18+
function pq_encode(RGB) {
19+
20+
const Yw = 203; // absolute luminance of media white, cd/m²
21+
const n = 2610 / (2 ** 14);
22+
const m = 2523 / (2 ** 5);
23+
const c1 = 3424 / (2 ** 12);
24+
const c2 = 2413 / (2 ** 7);
25+
const c3 = 2392 / (2 ** 7);
26+
27+
// given PQ encoded component in range [0, 1]
28+
// return media-white relative linear-light
29+
return RGB.map(function (val) {
30+
let x = Math.max(val * Yw / 10000, 0); // absolute luminance of peak white is 10,000 cd/m².
31+
let num = (c1 + (c2 * (x ** n)));
32+
let denom = (1 + (c3 * (x ** n)));
33+
34+
return ((num / denom) ** m);
35+
});
36+
}
37+
38+
function pq_decode(RGB) {
39+
40+
const Yw = 203; // absolute luminance of media white, cd/m²
41+
const ninv = (2 ** 14) / 2610;
42+
const minv = (2 ** 5) / 2523;
43+
const c1 = 3424 / (2 ** 12);
44+
const c2 = 2413 / (2 ** 7);
45+
const c3 = 2392 / (2 ** 7);
46+
47+
// given PQ encoded component in range [0, 1]
48+
// return media-white relative linear-light
49+
return RGB.map(function (val) {
50+
let x = ((Math.max(((val ** minv) - c1), 0) / (c2 - (c3 * (val ** minv)))) ** ninv);
51+
return (x * 10000 / Yw); // luminance relative to diffuse white, [0, 70 or so].
52+
});
53+
}

0 commit comments

Comments
 (0)