Skip to content

Commit 6d821a8

Browse files
authored
Update to support CSS HDR color syntax updates (#464)
* Update to support CSS HDR color syntax updates Jzazbz, JzCzhz, and ICtCp will all now use named color functions by default and serialize in the `color()` form with the custom names `--jzazbz`, `--jzczhz`, `--ictcp`. The non-hyphenated `color()` form is still supported but usage should be discontinued as it will be removed in the future at some point. * Ensure reference range of HDR colors match CSS
1 parent e6f6a2f commit 6d821a8

File tree

18 files changed

+308
-155
lines changed

18 files changed

+308
-155
lines changed

coloraide/color.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,9 @@
4646
from .spaces.rec2100_pq import Rec2100PQ
4747
from .spaces.rec2100_hlg import Rec2100HLG
4848
from .spaces.rec2100_linear import Rec2100Linear
49-
from .spaces.jzazbz import Jzazbz
50-
from .spaces.jzczhz import JzCzhz
51-
from .spaces.ictcp import ICtCp
49+
from .spaces.jzazbz.css import Jzazbz
50+
from .spaces.jzczhz.css import JzCzhz
51+
from .spaces.ictcp.css import ICtCp
5252
from .distance import DeltaE
5353
from .distance.delta_e_76 import DE76
5454
from .distance.delta_e_94 import DE94

coloraide/css/parse.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
RE_FUNC_END = re.compile(r'\s*(\))')
3131
RE_COMMA = re.compile(r'\s*(,)\s*')
3232
RE_SLASH = re.compile(r'\s*(/)\s*')
33-
RE_CSS_FUNC = re.compile(r'\b(color|rgba?|hsla?|hwb|(?:ok)?lab|(?:ok)?lch)\b')
33+
RE_CSS_FUNC = re.compile(r'\b(color|rgba?|hsla?|hwb|(?:ok)?lab|(?:ok)?lch|jzazbz|jzczhz|ictcp)\b')
3434

3535

3636
def norm_float(string: str) -> float:
@@ -426,13 +426,13 @@ def tokenize_css(css: str, start: int = 0) -> dict[str, Any]:
426426
if not validate_cylindrical_srgb(tokens):
427427
return {}
428428

429-
elif func_name in ('lab', 'oklab'):
429+
elif func_name in ('lab', 'oklab', 'jzazbz', 'ictcp'):
430430
tokens['id'] = '--' + func_name
431431

432432
if not validate_lab(tokens):
433433
return {}
434434

435-
elif func_name in ('oklch', 'lch'):
435+
elif func_name in ('oklch', 'lch', 'jzczhz'):
436436
tokens['id'] = '--' + func_name
437437

438438
if not validate_lch(tokens):
Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@
44
https://professional.dolby.com/siteassets/pdfs/ictcp_dolbywhitepaper_v071.pdf
55
"""
66
from __future__ import annotations
7-
from .lab import Lab
8-
from ..cat import WHITES
9-
from ..channels import Channel, FLG_MIRROR_PERCENT
10-
from .. import util
11-
from .. import algebra as alg
12-
from ..types import Vector
7+
from ..lab import Lab
8+
from ...cat import WHITES
9+
from ...channels import Channel, FLG_MIRROR_PERCENT
10+
from ... import util
11+
from ... import algebra as alg
12+
from ...types import Vector
1313

1414
# All PQ Values are equivalent to defaults as stated in link below:
1515
# https://en.wikipedia.org/wiki/High-dynamic-range_video#Perceptual_quantizer
@@ -93,8 +93,8 @@ class ICtCp(Lab):
9393
SERIALIZE = ("ictcp", "--ictcp",)
9494
CHANNELS = (
9595
Channel("i", 0.0, 1.0),
96-
Channel("ct", -1.0, 1.0, flags=FLG_MIRROR_PERCENT),
97-
Channel("cp", -1.0, 1.0, flags=FLG_MIRROR_PERCENT)
96+
Channel("ct", -0.5, 0.5, flags=FLG_MIRROR_PERCENT),
97+
Channel("cp", -0.5, 0.5, flags=FLG_MIRROR_PERCENT)
9898
)
9999
CHANNEL_ALIASES = {
100100
"intensity": "i",

coloraide/spaces/ictcp/css.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
"""ICtCp class."""
2+
from __future__ import annotations
3+
from .. import ictcp as base
4+
from ...css import parse
5+
from ...css import serialize
6+
from ...types import Vector
7+
from typing import Any, TYPE_CHECKING, Sequence
8+
9+
if TYPE_CHECKING: # pragma: no cover
10+
from ...color import Color
11+
12+
13+
class ICtCp(base.ICtCp):
14+
"""ICtCp class."""
15+
16+
def to_string(
17+
self,
18+
parent: Color,
19+
*,
20+
alpha: bool | None = None,
21+
precision: int | Sequence[int] | None = None,
22+
fit: bool | str | dict[str, Any] = True,
23+
none: bool = False,
24+
color: bool = False,
25+
percent: bool | Sequence[bool] = False,
26+
**kwargs: Any
27+
) -> str:
28+
"""Convert to CSS."""
29+
30+
return serialize.serialize_css(
31+
parent,
32+
func='ictcp',
33+
alpha=alpha,
34+
precision=precision,
35+
fit=fit,
36+
none=none,
37+
color=color,
38+
percent=percent
39+
)
40+
41+
def match(
42+
self,
43+
string: str,
44+
start: int = 0,
45+
fullmatch: bool = True
46+
) -> tuple[tuple[Vector, float], int] | None:
47+
"""Match a CSS color string."""
48+
49+
return parse.parse_css(self, string, start, fullmatch)
Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,12 @@
1010
If at some time that these assumptions are incorrect, we will be happy to alter the model.
1111
"""
1212
from __future__ import annotations
13-
from ..cat import WHITES
14-
from ..channels import Channel, FLG_MIRROR_PERCENT
15-
from .. import util
16-
from .. import algebra as alg
17-
from ..types import Vector, Matrix # noqa: F401
18-
from .lab import Lab
13+
from ...cat import WHITES
14+
from ...channels import Channel, FLG_MIRROR_PERCENT
15+
from ... import util
16+
from ... import algebra as alg
17+
from ...types import Vector, Matrix # noqa: F401
18+
from ..lab import Lab
1919

2020
B = 1.15
2121
G = 0.66
@@ -129,8 +129,8 @@ class Jzazbz(Lab):
129129
SERIALIZE = ("jzazbz", "--jzazbz",)
130130
CHANNELS = (
131131
Channel("jz", 0.0, 1.0),
132-
Channel("az", -1.0, 1.0, flags=FLG_MIRROR_PERCENT),
133-
Channel("bz", -1.0, 1.0, flags=FLG_MIRROR_PERCENT)
132+
Channel("az", -0.21, 0.21, flags=FLG_MIRROR_PERCENT),
133+
Channel("bz", -0.21, 0.21, flags=FLG_MIRROR_PERCENT)
134134
)
135135
CHANNEL_ALIASES = {
136136
"lightness": 'jz',

coloraide/spaces/jzazbz/css.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
"""Jzazbz class."""
2+
from __future__ import annotations
3+
from .. import jzazbz as base
4+
from ...css import parse
5+
from ...css import serialize
6+
from ...types import Vector
7+
from typing import Any, TYPE_CHECKING, Sequence
8+
9+
if TYPE_CHECKING: # pragma: no cover
10+
from ...color import Color
11+
12+
13+
class Jzazbz(base.Jzazbz):
14+
"""Jzazbz class."""
15+
16+
def to_string(
17+
self,
18+
parent: Color,
19+
*,
20+
alpha: bool | None = None,
21+
precision: int | Sequence[int] | None = None,
22+
fit: bool | str | dict[str, Any] = True,
23+
none: bool = False,
24+
color: bool = False,
25+
percent: bool | Sequence[bool] = False,
26+
**kwargs: Any
27+
) -> str:
28+
"""Convert to CSS."""
29+
30+
return serialize.serialize_css(
31+
parent,
32+
func='jzazbz',
33+
alpha=alpha,
34+
precision=precision,
35+
fit=fit,
36+
none=none,
37+
color=color,
38+
percent=percent
39+
)
40+
41+
def match(
42+
self,
43+
string: str,
44+
start: int = 0,
45+
fullmatch: bool = True
46+
) -> tuple[tuple[Vector, float], int] | None:
47+
"""Match a CSS color string."""
48+
49+
return parse.parse_css(self, string, start, fullmatch)
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@
44
https://www.osapublishing.org/oe/fulltext.cfm?uri=oe-25-13-15131&id=368272
55
"""
66
from __future__ import annotations
7-
from ..cat import WHITES
8-
from .lch import LCh
9-
from ..channels import Channel, FLG_ANGLE
7+
from ...cat import WHITES
8+
from ..lch import LCh
9+
from ...channels import Channel, FLG_ANGLE
1010

1111

1212
class JzCzhz(LCh):
@@ -31,7 +31,7 @@ class JzCzhz(LCh):
3131
}
3232
CHANNELS = (
3333
Channel("jz", 0.0, 1.0),
34-
Channel("cz", 0.0, 1.0),
34+
Channel("cz", 0.0, 0.26),
3535
Channel("hz", 0.0, 360.0, flags=FLG_ANGLE)
3636
)
3737

coloraide/spaces/jzczhz/css.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
"""JzCzhz class."""
2+
from __future__ import annotations
3+
from .. import jzczhz as base
4+
from ...css import parse
5+
from ...css import serialize
6+
from ...types import Vector
7+
from typing import Any, TYPE_CHECKING, Sequence
8+
9+
if TYPE_CHECKING: # pragma: no cover
10+
from ...color import Color
11+
12+
13+
class JzCzhz(base.JzCzhz):
14+
"""JzCzhz class."""
15+
16+
def to_string(
17+
self,
18+
parent: Color,
19+
*,
20+
alpha: bool | None = None,
21+
precision: int | Sequence[int] | None = None,
22+
fit: bool | str | dict[str, Any] = True,
23+
none: bool = False,
24+
color: bool = False,
25+
percent: bool | Sequence[bool] = False,
26+
**kwargs: Any
27+
) -> str:
28+
"""Convert to CSS."""
29+
30+
return serialize.serialize_css(
31+
parent,
32+
func='jzczhz',
33+
alpha=alpha,
34+
precision=precision,
35+
fit=fit,
36+
none=none,
37+
color=color,
38+
percent=percent
39+
)
40+
41+
def match(
42+
self,
43+
string: str,
44+
start: int = 0,
45+
fullmatch: bool = True
46+
) -> tuple[tuple[Vector, float], int] | None:
47+
"""Match a CSS color string."""
48+
49+
return parse.parse_css(self, string, start, fullmatch)

docs/src/markdown/about/changelog.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
# Changelog
22

3+
## 4.5.0
4+
5+
- **NEW**: Support change in CSS HDR spec that now specifies Jzazbz, JzCzhz, and ICtCp serialization as using the
6+
respective named color functions: `jzazbz()`, `jzczhz()`, and `ictcp()`. The `color()` will prefer the custom
7+
hyphenated forms using `--jzazbz`, `--jzczhz`, and `--ictcp` respectively. The non-hyphenated names are still
8+
supported in the `color()` form for backwards compatibility, but usage is discouraged as at some future time
9+
support for non-hyphenated names will be dropped as CSS has moved away from this as a supported convention.
10+
- **FIX**: Reference ranges for Jzazbz, JzCzhz, and ICtCp, now match the latest CSS HDR spec.
11+
312
## 4.4.1
413

514
- **FIX**: Fix XYB transform.

docs/src/markdown/advanced.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ clamped to the SDR range. If we convert an HDR color from Jzazbz to HSLuv, round
106106
simply does not support the HDR range.
107107

108108
```py play
109-
jz = Color('color(jzazbz 0.25 0 0)')
109+
jz = Color('jzazbz(0.25 0 0)')
110110
jz
111111
hsluv = jz.convert('hsluv')
112112
hsluv

0 commit comments

Comments
 (0)