Skip to content

Commit 28646c6

Browse files
authored
Add support for ICtCp color space (#216)
* Use power of two as a step for ranges approximator * Add support for ITP (ICtCp) color space
1 parent abc966e commit 28646c6

File tree

13 files changed

+230
-4
lines changed

13 files changed

+230
-4
lines changed

docs/api.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1547,6 +1547,7 @@ Mode | Color space | Definition object
15471547
`hsl` | HSL color space | `modeHsl`
15481548
`hsv` | HSV color space | `modeHsv`
15491549
`hwb` | HWB color space | `modeHwb`
1550+
`itp` | IC<sub>t</sub>C<sub>p</sub> color space | `modeItp`
15501551
`jab` | J<sub>z</sub>a<sub>z</sub>b<sub>z</sub> color space | `modeJab`
15511552
`jch` | J<sub>z</sub>a<sub>z</sub>b<sub>z</sub> in cylindrical form | `modeJch`
15521553
`lab` | CIELAB color space (D50 Illuminant) | `modeLab`

docs/color-spaces.md

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -438,7 +438,21 @@ It has the default _Chroma from Luma_ adjustment applied (effectively Y is subtr
438438
| ------- | ------------- | ----------- |
439439
| `x` | `[-0.0154, 0.0281]`| Cyan-red component |
440440
| `y` | `[0, 0.8453]`| Luma |
441-
| `b` | `[ -0.2778, 0.3880 ]`| Blue-yellow component |
441+
| `b` | `[ -0.2778, 0.3880 ]`| Blue-yellow component |
442+
443+
Does not have gamut limits.
444+
445+
#### `itp`
446+
447+
IC<sub>t</sub>C<sub>p</sub> (or ITP) color space, as defined in ITU-R Recommendation BT.2100.
448+
449+
| Channel | Range | Description |
450+
|---------|-------------------|-----------------------|
451+
| `i` | `[0, 0.581]`| Intensity |
452+
| `t` | `[-0.282, 0.278]`| Blue-yellow component |
453+
| `p` | `[-0.162, 0.279]`| Green–red component |
454+
455+
Serialized as `color(--ictcp i t p)`, with the `none` keyword for any missing color channel. An explicit `alpha < 1` is included as ` / alpha`.
442456

443457
Does not have gamut limits.
444458

docs/guides/tree-shaking.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ interpolate(['red', 'green'], 'lch');
125125

126126
Bootstrap all the color spaces available in Culori.
127127

128-
It provides the following named exports: `a98`, `cubehelix`, `dlab`, `dlch`, `hsi`, `hsl`, `hsv`, `hwb`, `jab`, `jch`, `lab`, `lab65`, `lch`, `lch65`, `lchuv`, `lrgb`, `luv`, `okhsl`, `okhsv`, `oklab`, `oklch`, `p3`, `prophoto`, `rec2020`, `rgb`, `xyb`, `xyz50`, `xyz65`, and `yiq`.
128+
It provides the following named exports: `a98`, `cubehelix`, `dlab`, `dlch`, `hsi`, `hsl`, `hsv`, `hwb`, `itp`, `jab`, `jch`, `lab`, `lab65`, `lch`, `lch65`, `lchuv`, `lrgb`, `luv`, `okhsl`, `okhsv`, `oklab`, `oklch`, `p3`, `prophoto`, `rec2020`, `rgb`, `xyb`, `xyz50`, `xyz65`, and `yiq`.
129129

130130
```js
131131
import 'culori/all';

src/bootstrap/all.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import modeHsi from '../hsi/definition.js';
77
import modeHsl from '../hsl/definition.js';
88
import modeHsv from '../hsv/definition.js';
99
import modeHwb from '../hwb/definition.js';
10+
import modeItp from '../itp/definition.js';
1011
import modeJab from '../jab/definition.js';
1112
import modeJch from '../jch/definition.js';
1213
import modeLab from '../lab/definition.js';
@@ -38,6 +39,7 @@ export const hsi = useMode(modeHsi);
3839
export const hsl = useMode(modeHsl);
3940
export const hsv = useMode(modeHsv);
4041
export const hwb = useMode(modeHwb);
42+
export const itp = useMode(modeItp);
4143
export const jab = useMode(modeJab);
4244
export const jch = useMode(modeJch);
4345
export const lab = useMode(modeLab);

src/index-fn.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export { default as modeHsi } from './hsi/definition.js';
77
export { default as modeHsl } from './hsl/definition.js';
88
export { default as modeHsv } from './hsv/definition.js';
99
export { default as modeHwb } from './hwb/definition.js';
10+
export { default as modeItp } from './itp/definition.js';
1011
export { default as modeJab } from './jab/definition.js';
1112
export { default as modeJch } from './jch/definition.js';
1213
export { default as modeLab } from './lab/definition.js';
@@ -171,6 +172,7 @@ export { default as convertHsiToRgb } from './hsi/convertHsiToRgb.js';
171172
export { default as convertHslToRgb } from './hsl/convertHslToRgb.js';
172173
export { default as convertHsvToRgb } from './hsv/convertHsvToRgb.js';
173174
export { default as convertHwbToRgb } from './hwb/convertHwbToRgb.js';
175+
export { default as convertItpToXyz65 } from './itp/convertItpToXyz65.js';
174176
export { default as convertJabToJch } from './jch/convertJabToJch.js';
175177
export { default as convertJabToRgb } from './jab/convertJabToRgb.js';
176178
export { default as convertJabToXyz65 } from './jab/convertJabToXyz65.js';
@@ -212,6 +214,7 @@ export { default as convertRgbToYiq } from './yiq/convertRgbToYiq.js';
212214
export { default as convertRgbToXyb } from './xyb/convertRgbToXyb.js';
213215
export { default as convertXybToRgb } from './xyb/convertXybToRgb.js';
214216
export { default as convertXyz65ToA98 } from './a98/convertXyz65ToA98.js';
217+
export { default as convertXyz65ToItp } from './itp/convertXyz65ToItp.js';
215218
export { default as convertXyz65ToJab } from './jab/convertXyz65ToJab.js';
216219
export { default as convertXyz65ToLab65 } from './lab65/convertXyz65ToLab65.js';
217220
export { default as convertXyz65ToP3 } from './p3/convertXyz65ToP3.js';

src/index.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import modeHsi from './hsi/definition.js';
77
import modeHsl from './hsl/definition.js';
88
import modeHsv from './hsv/definition.js';
99
import modeHwb from './hwb/definition.js';
10+
import modeItp from './itp/definition.js';
1011
import modeJab from './jab/definition.js';
1112
import modeJch from './jch/definition.js';
1213
import modeLab from './lab/definition.js';
@@ -172,6 +173,7 @@ export { default as convertHsiToRgb } from './hsi/convertHsiToRgb.js';
172173
export { default as convertHslToRgb } from './hsl/convertHslToRgb.js';
173174
export { default as convertHsvToRgb } from './hsv/convertHsvToRgb.js';
174175
export { default as convertHwbToRgb } from './hwb/convertHwbToRgb.js';
176+
export { default as convertItpToXyz65 } from './itp/convertItpToXyz65.js';
175177
export { default as convertJabToJch } from './jch/convertJabToJch.js';
176178
export { default as convertJabToRgb } from './jab/convertJabToRgb.js';
177179
export { default as convertJabToXyz65 } from './jab/convertJabToXyz65.js';
@@ -218,6 +220,7 @@ export { default as convertXyz50ToProphoto } from './prophoto/convertXyz50ToProp
218220
export { default as convertXyz50ToRgb } from './xyz50/convertXyz50ToRgb.js';
219221
export { default as convertXyz50ToXyz65 } from './xyz65/convertXyz50ToXyz65.js';
220222
export { default as convertXyz65ToA98 } from './a98/convertXyz65ToA98.js';
223+
export { default as convertXyz65ToItp } from './itp/convertXyz65ToItp.js';
221224
export { default as convertXyz65ToJab } from './jab/convertXyz65ToJab.js';
222225
export { default as convertXyz65ToLab65 } from './lab65/convertXyz65ToLab65.js';
223226
export { default as convertXyz65ToP3 } from './p3/convertXyz65ToP3.js';
@@ -235,6 +238,7 @@ export {
235238
modeHsl,
236239
modeHsv,
237240
modeHwb,
241+
modeItp,
238242
modeJab,
239243
modeJch,
240244
modeLab,
@@ -266,6 +270,7 @@ export const hsi = useMode(modeHsi);
266270
export const hsl = useMode(modeHsl);
267271
export const hsv = useMode(modeHsv);
268272
export const hwb = useMode(modeHwb);
273+
export const itp = useMode(modeItp);
269274
export const jab = useMode(modeJab);
270275
export const jch = useMode(modeJch);
271276
export const lab = useMode(modeLab);

src/itp/constants.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// PQ Constants
2+
// https://en.wikipedia.org/wiki/High-dynamic-range_video#Perceptual_quantizer
3+
export const M1 = 2610 / 16384;
4+
export const M2 = 2523 / 32;
5+
export const IM1 = 16384 / 2610;
6+
export const IM2 = 32 / 2523;
7+
export const C1 = 3424 / 4096;
8+
export const C2 = 2413 / 128;
9+
export const C3 = 2392 / 128;
10+
11+
// Maximum luminance in PQ is 10,000 cd/m^2
12+
// Relative XYZ has Y=1 for media white
13+
// BT.2048 says media white Y=203 at PQ 58
14+
//
15+
// This is confirmed here: https://www.itu.int/dms_pub/itu-r/opb/rep/R-REP-BT.2408-3-2019-PDF-E.pdf
16+
export const YW = 203;

src/itp/convertItpToXyz65.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { YW } from './constants.js';
2+
import { transferPqDecode } from '../hdr/transfer.js';
3+
4+
const convertItpToXyz65 = ({ i, t, p, alpha }) => {
5+
const [l, m, s] = [
6+
i + 0.008609037037932761 * t + 0.11102962500302593 * p,
7+
i - 0.00860903703793275 * t - 0.11102962500302599 * p,
8+
i + 0.5600313357106791 * t - 0.32062717498731885 * p
9+
].map(transferPqDecode);
10+
11+
const [x, y, z] = [
12+
2.0701522183894219 * l -
13+
1.3263473389671556 * m +
14+
0.2066510476294051 * s,
15+
0.3647385209748074 * l + 0.680566024947227 * m - 0.0453045459220346 * s,
16+
-0.049747207535812 * l - 0.0492609666966138 * m + 1.1880659249923042 * s
17+
].map(c => Math.max(c / YW, 0));
18+
19+
const res = { mode: 'xyz65', x, y, z };
20+
if (alpha !== undefined) {
21+
res.alpha = alpha;
22+
}
23+
24+
return res;
25+
};
26+
27+
export default convertItpToXyz65;

src/itp/convertXyz65ToItp.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { YW } from './constants.js';
2+
import { transferPqEncode } from '../hdr/transfer.js';
3+
4+
const convertXyz65ToItp = ({ x, y, z, alpha }) => {
5+
const [absX, absY, absZ] = [x, y, z].map(c => Math.max(c * YW, 0));
6+
const [l, m, s] = [
7+
0.3592832590121217 * absX +
8+
0.6976051147779502 * absY -
9+
0.0358915932320289 * absZ,
10+
-0.1920808463704995 * absX +
11+
1.1004767970374323 * absY +
12+
0.0753748658519118 * absZ,
13+
0.0070797844607477 * absX +
14+
0.0748396662186366 * absY +
15+
0.8433265453898765 * absZ
16+
].map(transferPqEncode);
17+
18+
const i = 0.5 * l + 0.5 * m;
19+
const t = 1.61376953125 * l + -3.323486328125 * m + 1.709716796875 * s;
20+
const p = 4.378173828125 * l + -4.24560546875 * m + -0.132568359375 * s;
21+
22+
const res = { mode: 'itp', i, t, p };
23+
if (alpha !== undefined) {
24+
res.alpha = alpha;
25+
}
26+
27+
return res;
28+
};
29+
30+
export default convertXyz65ToItp;

src/itp/definition.js

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { interpolatorLinear } from '../interpolate/linear.js';
2+
import { fixupAlpha } from '../fixup/alpha.js';
3+
import convertItpToXyz65 from './convertItpToXyz65.js';
4+
import convertXyz65ToItp from './convertXyz65ToItp.js';
5+
import { convertRgbToXyz65, convertXyz65ToRgb } from '../index.js';
6+
7+
/*
8+
ICtCp (or ITP) color space, as defined in ITU-R Recommendation BT.2100.
9+
10+
ICtCp is drafted to be supported in CSS within
11+
[CSS Color HDR Module Level 1](https://drafts.csswg.org/css-color-hdr/#ICtCp) spec.
12+
*/
13+
14+
const definition = {
15+
mode: 'itp',
16+
channels: ['i', 't', 'p', 'alpha'],
17+
parse: ['--ictcp'],
18+
serialize: '--ictcp',
19+
20+
toMode: {
21+
xyz65: convertItpToXyz65,
22+
rgb: color => convertXyz65ToRgb(convertItpToXyz65(color))
23+
},
24+
25+
fromMode: {
26+
xyz65: convertXyz65ToItp,
27+
rgb: color => convertXyz65ToItp(convertRgbToXyz65(color))
28+
},
29+
30+
ranges: {
31+
i: [0, 0.581],
32+
t: [-0.369, 0.272],
33+
p: [-0.164, 0.331]
34+
},
35+
36+
interpolate: {
37+
i: interpolatorLinear,
38+
t: interpolatorLinear,
39+
p: interpolatorLinear,
40+
alpha: { use: interpolatorLinear, fixup: fixupAlpha }
41+
}
42+
};
43+
44+
export default definition;

0 commit comments

Comments
 (0)