Repository: facelessuser/coloraide (language: python)

Issue:
Add at least Rec. 2100 PQ
This color proves useful to evaluate the HDR range of some of the included color spaces. We can consider something like Rec. 2100 HLG in the future, but we'd have to settle on some sane defaults and consider if we'd like to add tuning controls as that spaces as a couple non-color channel knobs. The actual implementation is pretty easy:

```py
class Rec2100PQ(SRGB):
    """Rec 2100 PQ class."""

    BASE = "rec2020"
    NAME = "rec2100pq"
    WHITE = WHITES['2deg']['D65']

    @classmethod
    def to_base(cls, coords: Vector) -> Vector:
        """To XYZ from Rec 2100 PQ."""

        return gam_2020(alg.divide(util.pq_st2084_eotf(coords), 203, dims=alg.D1_SC))

    @classmethod
    def from_base(cls, coords: Vector) -> Vector:
        """From XYZ to Rec 2100 PQ."""

        return util.pq_st2084_inverse_eotf(alg.multiply(lin_2020(coords), 203, dims=alg.D1_SC))
```

May make sense to expose linear color space of Rec 2020.


Discussion / hints:

Fix patch:
diff --git a/coloraide/color.py b/coloraide/color.py
index 3854d7ca6..fa2d56c92 100644
--- a/coloraide/color.py
+++ b/coloraide/color.py
@@ -24,9 +24,14 @@
 from .spaces.lab_d65 import LabD65
 from .spaces.lch_d65 import LchD65
 from .spaces.display_p3 import DisplayP3
+from .spaces.display_p3_linear import DisplayP3Linear
 from .spaces.a98_rgb import A98RGB
+from .spaces.a98_rgb_linear import A98RGBLinear
 from .spaces.prophoto_rgb import ProPhotoRGB
+from .spaces.prophoto_rgb_linear import ProPhotoRGBLinear
 from .spaces.rec2020 import Rec2020
+from .spaces.rec2020_linear import Rec2020Linear
+from .spaces.rec2100pq import Rec2100PQ
 from .spaces.xyz_d65 import XYZD65
 from .spaces.xyz_d50 import XYZD50
 from .spaces.oklab.css import Oklab
@@ -66,10 +71,11 @@
 )
 
 SUPPORTED_SPACES = (
-    HSL, HWB, Lab, Lch, LabD65, LchD65, SRGB, SRGBLinear, HSV,
-    DisplayP3, A98RGB, ProPhotoRGB, Rec2020, XYZD65, XYZD50,
-    Oklab, Oklch, Jzazbz, JzCzhz, ICtCp, Din99o, Lch99o, Luv, Lchuv,
-    Okhsl, Okhsv, HSLuv
+    XYZD65, XYZD50, SRGB, SRGBLinear, DisplayP3, DisplayP3Linear,
+    Oklab, Oklch, Lab, Lch, LabD65, LchD65, HSV, HSL, HWB, Rec2020, Rec2020Linear,
+    A98RGB, A98RGBLinear, ProPhotoRGB, ProPhotoRGBLinear,
+    Rec2100PQ, Jzazbz, JzCzhz, ICtCp, Din99o, Lch99o,
+    Luv, Lchuv, Okhsl, Okhsv, HSLuv
 )
 
 SUPPORTED_FIT = (
diff --git a/coloraide/spaces/a98_rgb.py b/coloraide/spaces/a98_rgb.py
index f8682ac31..a156a8495 100644
--- a/coloraide/spaces/a98_rgb.py
+++ b/coloraide/spaces/a98_rgb.py
@@ -4,37 +4,6 @@
 from .. import algebra as alg
 from ..types import Vector
 
-RGB_TO_XYZ = [
-    [0.5766690429101305, 0.1855582379065463, 0.1882286462349947],
-    [0.29734497525053605, 0.6273635662554661, 0.07529145849399788],
-    [0.02703136138641234, 0.07068885253582723, 0.9913375368376388]
-]
-
-XYZ_TO_RGB = [
-    [2.0415879038107465, -0.5650069742788596, -0.34473135077832956],
-    [-0.9692436362808795, 1.8759675015077202, 0.04155505740717558],
-    [0.013444280632031142, -0.11836239223101837, 1.0151749943912054]
-]
-
-
-def lin_a98rgb_to_xyz(rgb: Vector) -> Vector:
-    """
-    Convert an array of linear-light a98-rgb values to CIE XYZ using D50.D65.
-
-    (so no chromatic adaptation needed afterwards)
-    http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html
-    which has greater numerical precision than section 4.3.5.3 of
-    https://www.adobe.com/digitalimag/pdfs/AdobeRGB1998.pdf
-    """
-
-    return alg.dot(RGB_TO_XYZ, rgb, dims=alg.D2_D1)
-
-
-def xyz_to_lin_a98rgb(xyz: Vector) -> Vector:
-    """Convert XYZ to linear-light a98-rgb."""
-
-    return alg.dot(XYZ_TO_RGB, xyz, dims=alg.D2_D1)
-
 
 def lin_a98rgb(rgb: Vector) -> Vector:
     """Convert an array of a98-rgb values in the range 0.0 - 1.0 to linear light (un-corrected) form."""
@@ -51,7 +20,7 @@ def gam_a98rgb(rgb: Vector) -> Vector:
 class A98RGB(SRGB):
     """A98 RGB class."""
 
-    BASE = "xyz-d65"
+    BASE = "a98-rgb-linear"
     NAME = "a98-rgb"
     WHITE = WHITES['2deg']['D65']
 
@@ -59,10 +28,10 @@ class A98RGB(SRGB):
     def to_base(cls, coords: Vector) -> Vector:
         """To XYZ from A98 RGB."""
 
-        return lin_a98rgb_to_xyz(lin_a98rgb(coords))
+        return lin_a98rgb(coords)
 
     @classmethod
     def from_base(cls, coords: Vector) -> Vector:
         """From XYZ to A98 RGB."""
 
-        return gam_a98rgb(xyz_to_lin_a98rgb(coords))
+        return gam_a98rgb(coords)
diff --git a/coloraide/spaces/a98_rgb_linear.py b/coloraide/spaces/a98_rgb_linear.py
new file mode 100644
index 000000000..77f37eb7e
--- /dev/null
+++ b/coloraide/spaces/a98_rgb_linear.py
@@ -0,0 +1,57 @@
+"""Linear A98 RGB color class."""
+from ..cat import WHITES
+from .srgb import SRGB
+from .. import algebra as alg
+from ..types import Vector
+
+RGB_TO_XYZ = [
+    [0.5766690429101305, 0.1855582379065463, 0.1882286462349947],
+    [0.29734497525053605, 0.6273635662554661, 0.07529145849399788],
+    [0.02703136138641234, 0.07068885253582723, 0.9913375368376388]
+]
+
+XYZ_TO_RGB = [
+    [2.0415879038107465, -0.5650069742788596, -0.34473135077832956],
+    [-0.9692436362808795, 1.8759675015077202, 0.04155505740717558],
+    [0.013444280632031142, -0.11836239223101837, 1.0151749943912054]
+]
+
+
+def lin_a98rgb_to_xyz(rgb: Vector) -> Vector:
+    """
+    Convert an array of linear-light a98-rgb values to CIE XYZ using D50.D65.
+
+    (so no chromatic adaptation needed afterwards)
+    http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html
+    which has greater numerical precision than section 4.3.5.3 of
+    https://www.adobe.com/digitalimag/pdfs/AdobeRGB1998.pdf
+    """
+
+    return alg.dot(RGB_TO_XYZ, rgb, dims=alg.D2_D1)
+
+
+def xyz_to_lin_a98rgb(xyz: Vector) -> Vector:
+    """Convert XYZ to linear-light a98-rgb."""
+
+    return alg.dot(XYZ_TO_RGB, xyz, dims=alg.D2_D1)
+
+
+class A98RGBLinear(SRGB):
+    """Linear A98 RGB class."""
+
+    BASE = "xyz-d65"
+    NAME = "a98-rgb-linear"
+    SERIALIZE = ('--a98-rgb-linear',)
+    WHITE = WHITES['2deg']['D65']
+
+    @classmethod
+    def to_base(cls, coords: Vector) -> Vector:
+        """To XYZ from A98 RGB."""
+
+        return lin_a98rgb_to_xyz(coords)
+
+    @classmethod
+    def from_base(cls, coords: Vector) -> Vector:
+        """From XYZ to A98 RGB."""
+
+        return xyz_to_lin_a98rgb(coords)
diff --git a/coloraide/spaces/display_p3.py b/coloraide/spaces/display_p3.py
index a5fb03c0c..4970a5695 100644
--- a/coloraide/spaces/display_p3.py
+++ b/coloraide/spaces/display_p3.py
@@ -1,55 +1,13 @@
 """Display-p3 color class."""
 from ..cat import WHITES
 from .srgb import SRGB, lin_srgb, gam_srgb
-from .. import algebra as alg
 from ..types import Vector
 
-RGB_TO_XYZ = [
-    [0.4865709486482161, 0.26566769316909306, 0.1982172852343625],
-    [0.22897456406974875, 0.6917385218365063, 0.079286914093745],
-    [-3.972075516933487e-17, 0.04511338185890263, 1.043944368900976]
-]
-
-XYZ_TO_RGB = [
-    [2.4934969119414254, -0.9313836179191239, -0.40271078445071684],
-    [-0.8294889695615747, 1.7626640603183465, 0.02362468584194358],
-    [0.03584583024378446, -0.0761723892680418, 0.9568845240076872]
-]
-
-
-def lin_p3_to_xyz(rgb: Vector) -> Vector:
-    """
-    Convert an array of linear-light image-p3 values to CIE XYZ using  D65 (no chromatic adaptation).
-
-    http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html
-    """
-
-    # 0 was computed as -3.972075516933488e-17
-    return alg.dot(RGB_TO_XYZ, rgb, dims=alg.D2_D1)
-
-
-def xyz_to_lin_p3(xyz: Vector) -> Vector:
-    """Convert XYZ to linear-light P3."""
-
-    return alg.dot(XYZ_TO_RGB, xyz, dims=alg.D2_D1)
-
-
-def lin_p3(rgb: Vector) -> Vector:
-    """Convert an array of image-p3 RGB values in the range 0.0 - 1.0 to linear light (un-corrected) form."""
-
-    return lin_srgb(rgb)  # same as sRGB
-
-
-def gam_p3(rgb: Vector) -> Vector:
-    """Convert an array of linear-light image-p3 RGB  in the range 0.0-1.0 to gamma corrected form."""
-
-    return gam_srgb(rgb)  # same as sRGB
-
 
 class DisplayP3(SRGB):
     """Display-p3 class."""
 
-    BASE = "xyz-d65"
+    BASE = "display-p3-linear"
     NAME = "display-p3"
     WHITE = WHITES['2deg']['D65']
 
@@ -57,10 +15,10 @@ class DisplayP3(SRGB):
     def to_base(cls, coords: Vector) -> Vector:
         """To XYZ from Display P3."""
 
-        return lin_p3_to_xyz(lin_p3(coords))
+        return lin_srgb(coords)
 
     @classmethod
     def from_base(cls, coords: Vector) -> Vector:
         """From XYZ to Display P3."""
 
-        return gam_p3(xyz_to_lin_p3(coords))
+        return gam_srgb(coords)
diff --git a/coloraide/spaces/display_p3_linear.py b/coloraide/spaces/display_p3_linear.py
new file mode 100644
index 000000000..31c61b5ac
--- /dev/null
+++ b/coloraide/spaces/display_p3_linear.py
@@ -0,0 +1,55 @@
+"""Linear Display-p3 color class."""
+from ..cat import WHITES
+from .srgb import SRGB
+from .. import algebra as alg
+from ..types import Vector
+
+RGB_TO_XYZ = [
+    [0.4865709486482161, 0.26566769316909306, 0.1982172852343625],
+    [0.22897456406974875, 0.6917385218365063, 0.079286914093745],
+    [-3.972075516933487e-17, 0.04511338185890263, 1.043944368900976]
+]
+
+XYZ_TO_RGB = [
+    [2.4934969119414254, -0.9313836179191239, -0.40271078445071684],
+    [-0.8294889695615747, 1.7626640603183465, 0.02362468584194358],
+    [0.03584583024378446, -0.0761723892680418, 0.9568845240076872]
+]
+
+
+def lin_p3_to_xyz(rgb: Vector) -> Vector:
+    """
+    Convert an array of linear-light image-p3 values to CIE XYZ using  D65 (no chromatic adaptation).
+
+    http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html
+    """
+
+    # 0 was computed as -3.972075516933488e-17
+    return alg.dot(RGB_TO_XYZ, rgb, dims=alg.D2_D1)
+
+
+def xyz_to_lin_p3(xyz: Vector) -> Vector:
+    """Convert XYZ to linear-light P3."""
+
+    return alg.dot(XYZ_TO_RGB, xyz, dims=alg.D2_D1)
+
+
+class DisplayP3Linear(SRGB):
+    """Linear Display-p3 class."""
+
+    BASE = "xyz-d65"
+    NAME = "display-p3-linear"
+    SERIALIZE = ('--display-p3-linear',)
+    WHITE = WHITES['2deg']['D65']
+
+    @classmethod
+    def to_base(cls, coords: Vector) -> Vector:
+        """To XYZ from Linear Display P3."""
+
+        return lin_p3_to_xyz(coords)
+
+    @classmethod
+    def from_base(cls, coords: Vector) -> Vector:
+        """From XYZ to Linear Display P3."""
+
+        return xyz_to_lin_p3(coords)
diff --git a/coloraide/spaces/prophoto_rgb.py b/coloraide/spaces/prophoto_rgb.py
index 64edb80e0..88b8fa423 100644
--- a/coloraide/spaces/prophoto_rgb.py
+++ b/coloraide/spaces/prophoto_rgb.py
@@ -7,35 +7,6 @@
 ET = 1 / 512
 ET2 = 16 / 512
 
-RGB_TO_XYZ = [
-    [0.7977604896723027, 0.13518583717574031, 0.0313493495815248],
-    [0.2880711282292934, 0.7118432178101014, 8.565396060525902e-05],
-    [0.0, 0.0, 0.8251046025104601]
-]
-
-XYZ_TO_RGB = [
-    [1.3457989731028281, -0.2555801000799754, -0.05110628506753401],
-    [-0.5446224939028347, 1.5082327413132781, 0.02053603239147973],
-    [0.0, 0.0, 1.2119675456389454]
-]
-
-
-def lin_prophoto_to_xyz(rgb: Vector) -> Vector:
-    """
-    Convert an array of linear-light prophoto-rgb values to CIE XYZ using  D50.D50.
-
-    (so no chromatic adaptation needed afterwards)
-    http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html
-    """
-
-    return alg.dot(RGB_TO_XYZ, rgb, dims=alg.D2_D1)
-
-
-def xyz_to_lin_prophoto(xyz: Vector) -> Vector:
-    """Convert XYZ to linear-light prophoto-rgb."""
-
-    return alg.dot(XYZ_TO_RGB, xyz, dims=alg.D2_D1)
-
 
 def lin_prophoto(rgb: Vector) -> Vector:
     """
@@ -78,7 +49,7 @@ def gam_prophoto(rgb: Vector) -> Vector:
 class ProPhotoRGB(SRGB):
     """Pro Photo RGB class."""
 
-    BASE = "xyz-d50"
+    BASE = "prophoto-rgb-linear"
     NAME = "prophoto-rgb"
     WHITE = WHITES['2deg']['D50']
 
@@ -86,10 +57,10 @@ class ProPhotoRGB(SRGB):
     def to_base(cls, coords: Vector) -> Vector:
         """To XYZ from Pro Photo RGB."""
 
-        return lin_prophoto_to_xyz(lin_prophoto(coords))
+        return lin_prophoto(coords)
 
     @classmethod
     def from_base(cls, coords: Vector) -> Vector:
         """From XYZ to Pro Photo RGB."""
 
-        return gam_prophoto(xyz_to_lin_prophoto(coords))
+        return gam_prophoto(coords)
diff --git a/coloraide/spaces/prophoto_rgb_linear.py b/coloraide/spaces/prophoto_rgb_linear.py
new file mode 100644
index 000000000..95fcc688e
--- /dev/null
+++ b/coloraide/spaces/prophoto_rgb_linear.py
@@ -0,0 +1,55 @@
+"""Linear Pro Photo RGB color class."""
+from ..cat import WHITES
+from .srgb import SRGB
+from .. import algebra as alg
+from ..types import Vector
+
+RGB_TO_XYZ = [
+    [0.7977604896723027, 0.13518583717574031, 0.0313493495815248],
+    [0.2880711282292934, 0.7118432178101014, 8.565396060525902e-05],
+    [0.0, 0.0, 0.8251046025104601]
+]
+
+XYZ_TO_RGB = [
+    [1.3457989731028281, -0.2555801000799754, -0.05110628506753401],
+    [-0.5446224939028347, 1.5082327413132781, 0.02053603239147973],
+    [0.0, 0.0, 1.2119675456389454]
+]
+
+
+def lin_prophoto_to_xyz(rgb: Vector) -> Vector:
+    """
+    Convert an array of linear-light prophoto-rgb values to CIE XYZ using  D50.D50.
+
+    (so no chromatic adaptation needed afterwards)
+    http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html
+    """
+
+    return alg.dot(RGB_TO_XYZ, rgb, dims=alg.D2_D1)
+
+
+def xyz_to_lin_prophoto(xyz: Vector) -> Vector:
+    """Convert XYZ to linear-light prophoto-rgb."""
+
+    return alg.dot(XYZ_TO_RGB, xyz, dims=alg.D2_D1)
+
+
+class ProPhotoRGBLinear(SRGB):
+    """Linear Pro Photo RGB class."""
+
+    BASE = "xyz-d50"
+    NAME = "prophoto-rgb-linear"
+    SERIALIZE = ('--prophoto-rgb-linear',)
+    WHITE = WHITES['2deg']['D50']
+
+    @classmethod
+    def to_base(cls, coords: Vector) -> Vector:
+        """To XYZ from Linear Pro Photo RGB."""
+
+        return lin_prophoto_to_xyz(coords)
+
+    @classmethod
+    def from_base(cls, coords: Vector) -> Vector:
+        """From XYZ to Linear Pro Photo RGB."""
+
+        return xyz_to_lin_prophoto(coords)
diff --git a/coloraide/spaces/rec2020.py b/coloraide/spaces/rec2020.py
index a626da1e8..84a0b31ec 100644
--- a/coloraide/spaces/rec2020.py
+++ b/coloraide/spaces/rec2020.py
@@ -9,18 +9,6 @@
 BETA = 0.018053968510807
 BETA45 = 0.018053968510807 * 4.5
 
-RGB_TO_XYZ = [
-    [0.6369580483012914, 0.14461690358620832, 0.16888097516417208],
-    [0.2627002120112671, 0.6779980715188708, 0.05930171646986195],
-    [4.994106574466076e-17, 0.028072693049087428, 1.0609850577107909]
-]
-
-XYZ_TO_RGB = [
-    [1.7166511879712674, -0.35567078377639233, -0.25336628137365974],
-    [-0.6666843518324892, 1.6164812366349395, 0.015768545813911124],
-    [0.017639857445310787, -0.04277061325780853, 0.9421031212354739]
-]
-
 
 def lin_2020(rgb: Vector) -> Vector:
     """
@@ -58,27 +46,10 @@ def gam_2020(rgb: Vector) -> Vector:
     return result
 
 
-def lin_2020_to_xyz(rgb: Vector) -> Vector:
-    """
-    Convert an array of linear-light rec-2020 values to CIE XYZ using  D65.
-
-    (no chromatic adaptation)
-    http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html
-    """
-
-    return alg.dot(RGB_TO_XYZ, rgb, dims=alg.D2_D1)
-
-
-def xyz_to_lin_2020(xyz: Vector) -> Vector:
-    """Convert XYZ to linear-light rec-2020."""
-
-    return alg.dot(XYZ_TO_RGB, xyz, dims=alg.D2_D1)
-
-
 class Rec2020(SRGB):
     """Rec 2020 class."""
 
-    BASE = "xyz-d65"
+    BASE = "rec2020-linear"
     NAME = "rec2020"
     WHITE = WHITES['2deg']['D65']
 
@@ -86,10 +57,10 @@ class Rec2020(SRGB):
     def to_base(cls, coords: Vector) -> Vector:
         """To XYZ from Rec 2020."""
 
-        return lin_2020_to_xyz(lin_2020(coords))
+        return lin_2020(coords)
 
     @classmethod
     def from_base(cls, coords: Vector) -> Vector:
         """From XYZ to Rec 2020."""
 
-        return gam_2020(xyz_to_lin_2020(coords))
+        return gam_2020(coords)
diff --git a/coloraide/spaces/rec2020_linear.py b/coloraide/spaces/rec2020_linear.py
new file mode 100644
index 000000000..64280eab4
--- /dev/null
+++ b/coloraide/spaces/rec2020_linear.py
@@ -0,0 +1,59 @@
+"""Linear Rec 2020 color class."""
+from ..cat import WHITES
+from .srgb import SRGB
+from .. import algebra as alg
+from ..types import Vector
+
+ALPHA = 1.09929682680944
+BETA = 0.018053968510807
+BETA45 = 0.018053968510807 * 4.5
+
+RGB_TO_XYZ = [
+    [0.6369580483012914, 0.14461690358620832, 0.16888097516417208],
+    [0.2627002120112671, 0.6779980715188708, 0.05930171646986195],
+    [4.994106574466076e-17, 0.028072693049087428, 1.0609850577107909]
+]
+
+XYZ_TO_RGB = [
+    [1.7166511879712674, -0.35567078377639233, -0.25336628137365974],
+    [-0.6666843518324892, 1.6164812366349395, 0.015768545813911124],
+    [0.017639857445310787, -0.04277061325780853, 0.9421031212354739]
+]
+
+
+def lin_2020_to_xyz(rgb: Vector) -> Vector:
+    """
+    Convert an array of linear-light rec-2020 values to CIE XYZ using  D65.
+
+    (no chromatic adaptation)
+    http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html
+    """
+
+    return alg.dot(RGB_TO_XYZ, rgb, dims=alg.D2_D1)
+
+
+def xyz_to_lin_2020(xyz: Vector) -> Vector:
+    """Convert XYZ to linear-light rec-2020."""
+
+    return alg.dot(XYZ_TO_RGB, xyz, dims=alg.D2_D1)
+
+
+class Rec2020Linear(SRGB):
+    """Linear Rec 2020 class."""
+
+    BASE = "xyz-d65"
+    NAME = "rec2020-linear"
+    SERIALIZE = ('--rec2020-linear',)
+    WHITE = WHITES['2deg']['D65']
+
+    @classmethod
+    def to_base(cls, coords: Vector) -> Vector:
+        """To XYZ from Linear Rec 2020."""
+
+        return lin_2020_to_xyz(coords)
+
+    @classmethod
+    def from_base(cls, coords: Vector) -> Vector:
+        """From XYZ to Linear Rec 2020."""
+
+        return xyz_to_lin_2020(coords)
diff --git a/coloraide/spaces/rec2100pq.py b/coloraide/spaces/rec2100pq.py
new file mode 100644
index 000000000..955f00566
--- /dev/null
+++ b/coloraide/spaces/rec2100pq.py
@@ -0,0 +1,31 @@
+"""
+Rec 2100 PQ color class.
+
+https://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.2100-2-201807-I!!PDF-E.pdf
+"""
+from ..cat import WHITES
+from .srgb import SRGB
+from .. import algebra as alg
+from ..types import Vector
+from .. import util
+
+
+class Rec2100PQ(SRGB):
+    """Rec 2100 PQ class."""
+
+    BASE = "rec2020-linear"
+    NAME = "rec2100pq"
+    SERIALIZE = ('--rec2100pq',)
+    WHITE = WHITES['2deg']['D65']
+
+    @classmethod
+    def to_base(cls, coords: Vector) -> Vector:
+        """To XYZ from Rec 2100 PQ."""
+
+        return alg.divide(util.pq_st2084_eotf(coords), util.YW, dims=alg.D1_SC)
+
+    @classmethod
+    def from_base(cls, coords: Vector) -> Vector:
+        """From XYZ to Rec 2100 PQ."""
+
+        return util.pq_st2084_inverse_eotf(alg.multiply(coords, util.YW, dims=alg.D1_SC))
diff --git a/docs/src/dictionary/en-custom.txt b/docs/src/dictionary/en-custom.txt
index f10de1e10..dcf3ebba1 100644
--- a/docs/src/dictionary/en-custom.txt
+++ b/docs/src/dictionary/en-custom.txt
@@ -54,6 +54,7 @@ Fairchild
 Florian
 Formatter
 HDR
+HLG
 HSB
 HSL
 HSL
diff --git a/docs/src/markdown/colors/index.md b/docs/src/markdown/colors/index.md
index 6e7eaf5ea..9df6825c3 100644
--- a/docs/src/markdown/colors/index.md
+++ b/docs/src/markdown/colors/index.md
@@ -121,12 +121,13 @@ _[Learn about sRGB](https://en.wikipedia.org/wiki/SRGB)_
 
 <figure markdown="1">
 
-![sRGB](../images/srgb-linear.png)
+![sRGB](../images/srgb.png)
 
 <figcaption>CIE 1931 xy Chromaticity -- sRGB Linear Chromaticities</figcaption>
 </figure>
 
-The sRGB Linear space is the same as [sRGB](#srgb) *except* that the transfer function is linear-light (there is no gamma-encoding).
+The sRGB Linear space is the same as [sRGB](#srgb) *except* that the transfer function is linear-light (there is no
+gamma-encoding).
 
 _[Learn about sRGB](https://en.wikipedia.org/wiki/SRGB)_
 </div>
@@ -234,6 +235,74 @@ _[Learn about Display P3](https://www.color.org/chardata/rgb/DisplayP3.xalter)_
         Color("display-p3", [0, 0, 0], 1).to_string()
         ```
 
+### Display P3 Linear
+
+<div class="info-container" markdown="1">
+!!! info inline end "Properties"
+
+    **Name:** `display-p3-linear`
+
+    **White Point:** D65
+
+    **Coordinates:**
+
+    Name | Range^\*^
+    ---- | -----
+    r    | [0, 1]
+    g    | [0, 1]
+    b    | [0, 1]
+
+    ^\*^ Range denotes _in gamut_ colors, but the color space supports an extended range beyond the gamut.
+
+<figure markdown="1">
+
+![Display P3](../images/display-p3.png)
+
+<figcaption>CIE 1931 xy Chromaticity -- Display P3 Chromaticities</figcaption>
+</figure>
+
+The Linear Display P3 space is the same as [Display P3](#display-p3) *except* that the transfer function is linear-light
+(there is no gamma-encoding).
+
+_[Learn about Display P3](https://www.color.org/chardata/rgb/DisplayP3.xalter)_
+</div>
+
+??? abstract "ColorAide Details"
+
+    **Channel Aliases:**
+    : 
+        Channels | Aliases
+        -------- | -------
+        `r`      | `red`
+        `g`      | `green`
+        `b`      | `blue`
+
+    **Inputs**
+    : 
+        Linear Display P3 is not supported via the CSS spec and the parser input and string output only supports the
+        `#!css-color color()` function format using the custom name `#!css-color --display-p3-linear`:
+
+        ```css-color
+        color(--display-p3-linear r g b / a)  // Color function
+        ```
+
+        When manually creating a color via raw data or specifying a color space as a parameter in a function, the color
+        space name is always used:
+
+        ```py
+        Color("display-p3-linear", [0, 0, 0], 1)
+        ```
+
+    **Output**
+    : 
+        The string representation of the color object and the default string output will be in the
+        `#!css-color color(--display-p3-linear r g b / a)` form.
+
+        ```playground
+        Color("display-p3-linear", [0, 0, 0], 1)
+        Color("display-p3-linear", [0, 0, 0], 1).to_string()
+        ```
+
 ### A98 RGB
 
 <div class="info-container" markdown="1">
@@ -305,6 +374,75 @@ _[Learn about A98 RGB](https://en.wikipedia.org/wiki/Adobe_RGB_color_space)_
         Color("a98-rgb", [0, 0, 0], 1).to_string()
         ```
 
+### A98 RGB Linear
+
+<div class="info-container" markdown="1">
+
+!!! info inline end "Properties"
+
+    **Name:** `a98-rgb-linear`
+
+    **White Point:** D65
+
+    **Coordinates:**
+
+    Name | Range^\*^
+    ---- | -----
+    r    | [0, 1]
+    g    | [0, 1]
+    b    | [0, 1]
+
+    ^\*^ Range denotes _in gamut_ colors, but the color space supports an extended range beyond the gamut.
+
+<figure markdown="1">
+
+![A98 RGB](../images/a98-rgb.png)
+
+<figcaption>CIE 1931 xy Chromaticity -- Adobe RGB 1998 Chromaticities</figcaption>
+</figure>
+
+The Linear A98 RGB space is the same as [A98 RGB](#a98-rgb) *except* that the transfer function is linear-light (there
+is no gamma-encoding).
+
+_[Learn about A98 RGB](https://en.wikipedia.org/wiki/Adobe_RGB_color_space)_
+</div>
+
+??? abstract "ColorAide Details"
+
+    **Channel Aliases:**
+    : 
+        Channels | Aliases
+        -------- | -------
+        `r`      | `red`
+        `g`      | `green`
+        `b`      | `blue`
+
+    **Inputs:**
+    : 
+        Linear A98 RGB is not supported via the CSS spec and the parser input and string output only supports the
+        `#!css-color color()` function format using the custom name `#!css-color --a98-rgb-linear`:
+
+        ```css-color
+        color(--a98-rgb-linear r g b / a)  // Color function
+        ```
+
+        When manually creating a color via raw data or specifying a color space as a parameter in a function, the color
+        space name is always used:
+
+        ```py
+        Color("a98-rgb-linear", [0, 0, 0], 1)
+        ```
+
+    **Output**
+    : 
+        The string representation of the color object and the default string output will be in the
+        `#!css-color color(--a98-rgb-linear r g b / a)` form.
+
+        ```playground
+        Color("a98-rgb-linear", [0, 0, 0], 1)
+        Color("a98-rgb-linear", [0, 0, 0], 1).to_string()
+        ```
+
 ### REC. 2020
 
 <div class="info-container" markdown="1">
@@ -331,11 +469,10 @@ _[Learn about A98 RGB](https://en.wikipedia.org/wiki/Adobe_RGB_color_space)_
 <figcaption>CIE 1931 xy Chromaticity -- Rec. 2020 Chromaticities</figcaption>
 </figure>
 
-The Rec. 2020 color space is a very wide gamut RGB color space which is used in 4k and 8k UHDTV. ITU-R Recommendation
-BT.2020, more commonly known by the abbreviations Rec. 2020 or BT.2020, defines various aspects of ultra-high-definition
-television (UHDTV) with standard dynamic range (SDR) and wide color gamut (WCG), including picture resolutions, frame
-rates with progressive scan, bit depths, color primaries, RGB and luma-chroma color representations, chroma
-subsamplings, and an opto-electronic transfer function.
+ITU-R Recommendation BT.2020, more commonly known by the abbreviations Rec. 2020 or BT.2020, defines various aspects of
+ultra-high-definition television (UHDTV) with standard dynamic range (SDR) and wide color gamut (WCG), including picture
+resolutions, frame rates with progressive scan, bit depths, color primaries, RGB and luma-chroma color representations,
+chroma subsamplings, and an opto-electronic transfer function. The color is used in 4k and 8k UHDTV.
 
 _[Learn about REC.2020](https://en.wikipedia.org/wiki/Rec._2020)_
 
@@ -377,6 +514,76 @@ _[Learn about REC.2020](https://en.wikipedia.org/wiki/Rec._2020)_
         Color("rec2020", [0, 0, 0], 1).to_string()
         ```
 
+### REC. 2020 Linear
+
+<div class="info-container" markdown="1">
+!!! info inline end "Properties"
+
+    **Name:** `rec2020-linear`
+
+    **White Point:** D65
+
+    **Coordinates:**
+
+    Name | Range^\*^
+    ---- | -----
+    r    | [0, 1]
+    g    | [0, 1]
+    b    | [0, 1]
+
+    ^\*^ Range denotes _in gamut_ colors, but the color space supports an extended range beyond the gamut.
+
+<figure markdown="1">
+
+![Rec. 2020](../images/rec2020.png)
+
+<figcaption>CIE 1931 xy Chromaticity -- Rec. 2020 Chromaticities</figcaption>
+</figure>
+
+The Linear Rec. 2020 space is the same as [Rec. 2020](#rec-2020) *except* that the transfer function is linear-light
+(there is no gamma-encoding).
+
+_[Learn about REC.2020](https://en.wikipedia.org/wiki/Rec._2020)_
+
+</div>
+
+??? abstract "ColorAide Details"
+
+    **Channel Aliases:**
+    : 
+        Channels | Aliases
+        -------- | -------
+        `r`      | `red`
+        `g`      | `green`
+        `b`      | `blue`
+
+    **Inputs:**
+    : 
+
+        Linear Rec. 2020 is not supported via the CSS spec and the parser input and string output only supports the
+        `#!css-color color()` function format using the custom name `#!css-color --rec2020-linear`:
+
+        ```css-color
+        color(--rec2020-linear r g b / a)  // Color function
+        ```
+
+        When manually creating a color via raw data or specifying a color space as a parameter in a function, the color
+        space name is always used:
+
+        ```py
+        Color("rec2020-linear", [0, 0, 0], 1)
+        ```
+
+    **Output:**
+    : 
+        The string representation of the color object and the default string output will be in the
+        `#!css-color color(--rec2020-linear r g b / a)` form.
+
+        ```playground
+        Color("rec2020-linear", [0, 0, 0], 1)
+        Color("rec2020-linear", [0, 0, 0], 1).to_string()
+        ```
+
 ### ProPhoto
 
 <div class="info-container" markdown="1">
@@ -408,6 +615,9 @@ space developed by Kodak. It offers an especially large gamut designed for use w
 ProPhoto RGB color space encompasses over 90% of possible surface colors in the [CIE L\*a\*b\*](#cielab) color space,
 and 100% of likely occurring real-world surface colors documented by Pointer in 1980.
 
+_[Learn about ProPhoto](https://en.wikipedia.org/wiki/ProPhoto_RGB_color_space)_
+</div>
+
 ??? abstract "ColorAide Details"
 
     **Channel Aliases:**
@@ -442,9 +652,145 @@ and 100% of likely occurring real-world surface colors documented by Pointer in
         Color("prophoto-rgb", [0, 0, 0], 1).to_string()
         ```
 
+### ProPhoto Linear
+
+<div class="info-container" markdown="1">
+!!! info inline end "Properties"
+
+    **Name:** `prophoto-rgb-linear`
+
+    **White Point:** D50
+
+    **Coordinates:**
+
+    Name | Range^\*^
+    ---- | -----
+    r    | [0, 1]
+    g    | [0, 1]
+    b    | [0, 1]
+
+    ^\*^ Range denotes _in gamut_ colors, but the color space supports an extended range beyond the gamut.
+
+<figure markdown="1">
+
+![ProPhoto RGB](../images/prophoto-rgb.png)
+
+<figcaption>CIE 1931 xy Chromaticity -- ProPhoto RGB Chromaticities</figcaption>
+</figure>
+
+The Linear ProPhoto space is the same as [ProPhoto](#prophoto) *except* that the transfer function is linear-light
+(there is no gamma-encoding).
+
 _[Learn about ProPhoto](https://en.wikipedia.org/wiki/ProPhoto_RGB_color_space)_
 </div>
 
+??? abstract "ColorAide Details"
+
+    **Channel Aliases:**
+    : 
+        Channels | Aliases
+        -------- | -------
+        `r`      | `red`
+        `g`      | `green`
+        `b`      | `blue`
+
+    **Inputs:**
+    : 
+        Linear ProPhoto is not supported via the CSS spec and the parser input and string output only supports the
+        `#!css-color color()` function format using the custom name `#!css-color --prophoto-rgb-linear`:
+
+        ```css-color
+        color(--prophoto-rgb-linear r g b / a)  // Color function
+        ```
+
+        When manually creating a color via raw data or specifying a color space as a parameter in a function, the color
+        space name is always used:
+
+        ```py
+        Color("prophoto-rgb-linear", [0, 0, 0], 1)
+        ```
+    **Output:**
+    : 
+        The string representation of the color object and the default string output will be in the
+        `#!css-color color(--prophoto-rgb-linear r g b / a)` form.
+
+        ```playground
+        Color("prophoto-rgb-linear", [0, 0, 0], 1)
+        Color("prophoto-rgb-linear", [0, 0, 0], 1).to_string()
+        ```
+
+### REC. 2100 PQ
+
+<div class="info-container" markdown="1">
+!!! info inline end "Properties"
+
+    **Name:** `rec2100pq`
+
+    **White Point:** D65
+
+    **Coordinates:**
+
+    Name | Range
+    ---- | -----
+    r    | [0, 1]
+    g    | [0, 1]
+    b    | [0, 1]
+
+<figure markdown="1">
+
+![Rec. 2020](../images/rec2020.png)
+
+<figcaption>CIE 1931 xy Chromaticity -- Rec. 2100 Chromaticities (Same as Rec. 2020)</figcaption>
+</figure>
+
+BT.2100, more commonly known by the abbreviations Rec. 2100 or BT.2100, introduced high-dynamic-range television
+(HDR-TV) by recommending the use of the perceptual quantizer (PQ) or hybrid log–gamma (HLG) transfer functions instead
+of the traditional "gamma" previously used for SDR-TV. Rec. 2100 PQ specifically uses the perceptual quantizer.
+
+The actual gamut of Rec. 2100 uses the same wide color gamut of Rec. 2020, but the color space itself supports an HDR
+range.
+
+_[Learn about REC.2020](https://en.wikipedia.org/wiki/Rec._2100)_
+
+</div>
+
+??? abstract "ColorAide Details"
+
+    **Channel Aliases:**
+    : 
+        Channels | Aliases
+        -------- | -------
+        `r`      | `red`
+        `g`      | `green`
+        `b`      | `blue`
+
+    **Inputs:**
+    : 
+
+        Rec. 2100 PQ is not supported via the CSS spec and the parser input and string output only supports the
+        `#!css-color color()` function format using the custom name `#!css-color --rec2100pq`:
+
+        ```css-color
+        color(--rec2100pq r g b / a)  // Color function
+        ```
+
+        When manually creating a color via raw data or specifying a color space as a parameter in a function, the color
+        space name is always used:
+
+        ```py
+        Color("rec2100pq", [0, 0, 0], 1)
+        ```
+
+    **Output:**
+    : 
+        The string representation of the color object and the default string output will be in the
+        `#!css-color color(--rec2100pq r g b / a)` form.
+
+        ```playground
+        Color("rec2100pq", [0, 0, 0], 1)
+        Color("rec2100pq", [0, 0, 0], 1).to_string()
+        ```
+
 ## Cylindrical sRGB Spaces
 
 The sRGB color space has been represented in a number of cylindrical models. Each model was an attempt to either align
@@ -913,7 +1259,7 @@ can be represented with other white points as well. CSS actually allows using ei
 
 ![XYZ D65 3D](../images/xyz-d65-3d.png)
 
-<figcaption markdown="1">Approximation[^1] of the sRGB gamut represented within the XYZ D65 color space.</figcaption>
+<figcaption markdown="1">The sRGB gamut represented within the XYZ D65 color space.</figcaption>
 </figure>
 
 The CIE 1931 RGB color space and CIE 1931 XYZ color space were created by the International Commission on Illumination
@@ -986,7 +1332,7 @@ _[Learn about XYZ](https://en.wikipedia.org/wiki/CIE_1931_color_space)_
 
 ![XYZ D50 3D](../images/xyz-d50-3d.png)
 
-<figcaption markdown="1">Approximation[^1] of the sRGB gamut represented within the XYZ D50 color space.</figcaption>
+<figcaption markdown="1">The sRGB gamut represented within the XYZ D50 color space.</figcaption>
 </figure>
 
 XYZ D50 is the same as [XYZ D65](#xyz-d65) except it uses a D50 white point.
@@ -1061,7 +1407,7 @@ also included variants with D65 white points as well.
 
 ![CIELAB D50 3D](../images/lab-3d.png)
 
-<figcaption markdown="1">Approximation[^1] of the sRGB gamut represented within the CIELAB D50 color space.</figcaption>
+<figcaption markdown="1">The sRGB gamut represented within the CIELAB D50 color space.</figcaption>
 </figure>
 
 The CIELAB color space also referred to as L\*a\*b\* is a color space defined by the International Commission on
@@ -1134,7 +1480,7 @@ _[Learn about CIELAB](https://en.wikipedia.org/wiki/CIELAB_color_space)_
 
 ![CIELAB D65 3D](../images/lab-d65-3d.png)
 
-<figcaption markdown="1">Approximation[^1] of the sRGB gamut represented within the CIELAB D50 color space.</figcaption>
+<figcaption markdown="1">The sRGB gamut represented within the CIELAB D50 color space.</figcaption>
 </figure>
 
 CIELAB D65 is the same as [CIELAB](#cielab-d50) except it uses a D65 white point.
@@ -1209,7 +1555,7 @@ D65 white point.
 
 ![CIELCH D50 3D](../images/lch-3d.png)
 
-<figcaption markdown="1">Approximation[^1] of the sRGB gamut represented within the CIELCH D50 color space.</figcaption>
+<figcaption markdown="1">The sRGB gamut represented within the CIELCH D50 color space.</figcaption>
 </figure>
 
 The "CIELCH" or "CIEHLC" space is a color space based on [CIELAB](#cielab), which uses the polar coordinates C\*
@@ -1280,7 +1626,7 @@ _[Learn about CIELCH](https://en.wikipedia.org/wiki/CIELAB_color_space#Cylindric
 
 ![CIELCH D65 3D](../images/lch-d65-3d.png)
 
-<figcaption markdown="1">Approximation[^1] of the sRGB gamut represented within the CIELCH D65 color space.</figcaption>
+<figcaption markdown="1">The sRGB gamut represented within the CIELCH D65 color space.</figcaption>
 </figure>
 
 CIELCH D65 is the same as [CIELCH](#cielch-d50) except it uses a D65 white point.
@@ -1348,7 +1694,7 @@ _[Learn about CIELCH](https://en.wikipedia.org/wiki/CIELAB_color_space#Cylindric
 
 ![Oklab](../images/oklab-3d.png)
 
-<figcaption markdown="1">Approximation[^1] of the sRGB gamut represented within the Oklab color space.</figcaption>
+<figcaption markdown="1">The sRGB gamut represented within the Oklab color space.</figcaption>
 </figure>
 
 
@@ -1419,7 +1765,7 @@ _[Learn about Oklab](https://bottosson.github.io/posts/oklab/)_
 
 ![Oklch](../images/oklch-3d.png)
 
-<figcaption markdown="1">Approximation[^1] of the sRGB gamut represented within the Oklch color space.</figcaption>
+<figcaption markdown="1">The sRGB gamut represented within the Oklch color space.</figcaption>
 </figure>
 
 
@@ -1483,7 +1829,7 @@ _[Learn about Oklch](https://bottosson.github.io/posts/oklab/)_
 
 ![CIELUV 3D](../images/luv-3d.png)
 
-<figcaption markdown="1">Approximation[^1] of the sRGB gamut represented within the CIELUV D65 color space.</figcaption>
+<figcaption markdown="1">The sRGB gamut represented within the CIELUV D65 color space.</figcaption>
 </figure>
 
 CIELUV is similar to CIELAB as they were both developed in 1976 as perceptually uniform color spaces, both are derived
@@ -1559,7 +1905,7 @@ _[Learn about CIELUV](https://en.wikipedia.org/wiki/CIELUV)_
 
 ![CIELCH~uv~](../images/lchuv-3d.png)
 
-<figcaption markdown="1">Approximation[^1] of the sRGB gamut represented within the CIELCH~uv~ color space.</figcaption>
+<figcaption markdown="1">The sRGB gamut represented within the CIELCH~uv~ color space.</figcaption>
 </figure>
 
 [CIELUV](#cieluv) is not an intuitive space to work with directly and instead is often converted to cylindrical
@@ -1629,7 +1975,7 @@ _[Learn about CIELCH~uv~](https://en.wikipedia.org/wiki/CIELUV)_
 
 ![Jzazbz](../images/jzazbz-3d.png)
 
-<figcaption markdown="1">Approximation[^1] of the sRGB gamut represented within the Jzazbz color space.</figcaption>
+<figcaption markdown="1">The sRGB gamut represented within the Jzazbz color space.</figcaption>
 </figure>
 
 Jzazbz is a a color space designed for perceptual uniformity in high dynamic range (HDR) and wide color gamut (WCG)
@@ -1703,7 +2049,7 @@ _[Learn about Jzazbz](https://www.osapublishing.org/oe/fulltext.cfm?uri=oe-25-13
 
 ![JzCzhz](../images/jzczhz-3d.png)
 
-<figcaption markdown="1">Approximation[^1] of the sRGB gamut represented within the JzCzhz color space.</figcaption>
+<figcaption markdown="1">The sRGB gamut represented within the JzCzhz color space.</figcaption>
 </figure>
 
 JzCzhz is the cylindrical form of [Jzazbz](#jzazbz).
@@ -1771,7 +2117,7 @@ _[Learn about JzCzhz](https://www.osapublishing.org/oe/fulltext.cfm?uri=oe-25-13
 
 ![ICtCp](../images/ictcp-3d.png)
 
-<figcaption markdown="1">Approximation[^1] of the sRGB gamut represented within the ICtCp color space.</figcaption>
+<figcaption markdown="1">The sRGB gamut represented within the ICtCp color space.</figcaption>
 </figure>
 
 ICtCp is a color space format with better perceptual uniformity than [CIELAB](#cielab) and is used as a part of the
@@ -1842,7 +2188,7 @@ _[Learn about ICtCp](https://en.wikipedia.org/wiki/ICtCp)_
 
 ![DIN99o](../images/din99o-3d.png)
 
-<figcaption markdown="1">Approximation[^1] of the sRGB gamut represented within the DIN99o color space.</figcaption>
+<figcaption markdown="1">The sRGB gamut represented within the DIN99o color space.</figcaption>
 </figure>
 
 The DIN99 color space system is a further development of the CIELAB color space system developed by the FNF / FNL 2
@@ -1914,7 +2260,7 @@ _[Learn about DIN99o](https://de.wikipedia.org/wiki/DIN99-Farbraum)_
 
 ![DIN99o Lch](../images/lch99o-3d.png)
 
-<figcaption markdown="1">Approximation[^1] of the sRGB gamut represented within the DIN99o Lch color space.</figcaption>
+<figcaption markdown="1">The sRGB gamut represented within the DIN99o Lch color space.</figcaption>
 </figure>
 
 DIN99o Lch is the cylindrical form of [DIN99o](#din99o).
@@ -1958,10 +2304,6 @@ _[Learn about DIN99o Lch](https://de.wikipedia.org/wiki/DIN99-Farbraum)_
         Color("lch99o", [0, 0, 0], 1).to_string()
         ```
 
-[^1]: For the purposes of speed, 3D models are calculated by taking points on the outer shell of the sRGB gamut and
-mapping those points to the color space of interest. While a more accurate model could be made by taking all points into
-account, this approach gives a fairly good approximation of the shape of the sRGB gamut within the targeted color space.
-
 <style>
 .info-container {display: inline-block;}
 </style>
diff --git a/docs/src/markdown/images/srgb-linear.png b/docs/src/markdown/images/srgb-linear.png
deleted file mode 100644
index b7b1287e5..000000000
Binary files a/docs/src/markdown/images/srgb-linear.png and /dev/null differ


Test patch:
diff --git a/tests/test_a98_rgb_linear.py b/tests/test_a98_rgb_linear.py
new file mode 100644
index 000000000..2a2df2fc6
--- /dev/null
+++ b/tests/test_a98_rgb_linear.py
@@ -0,0 +1,64 @@
+"""Test Linear A98 RGB."""
+import unittest
+from . import util
+from coloraide import Color
+import pytest
+
+
+class TestLinearA98RGB(util.ColorAssertsPyTest):
+    """Test Linear A98 RGB."""
+
+    COLORS = [
+        ('red', 'color(--a98-rgb-linear 0.71513 0 0)'),
+        ('orange', 'color(--a98-rgb-linear 0.82231 0.37626 0.01549)'),
+        ('yellow', 'color(--a98-rgb-linear 1 1 0.04116)'),
+        ('green', 'color(--a98-rgb-linear 0.06149 0.21586 0.00889)'),
+        ('blue', 'color(--a98-rgb-linear 0 0 0.95884)'),
+        ('indigo', 'color(--a98-rgb-linear 0.05032 0 0.21404)'),
+        ('violet', 'color(--a98-rgb-linear 0.67502 0.22323 0.82899)'),
+        ('white', 'color(--a98-rgb-linear 1 1 1)'),
+        ('gray', 'color(--a98-rgb-linear 0.21586 0.21586 0.21586)'),
+        ('black', 'color(--a98-rgb-linear 0 0 0)')
+    ]
+
+    @pytest.mark.parametrize('color1,color2', COLORS)
+    def test_colors(self, color1, color2):
+        """Test colors."""
+
+        self.assertColorEqual(Color(color1).convert('a98-rgb-linear'), Color(color2))
+
+
+class TestLinearA98RGBPoperties(util.ColorAsserts, unittest.TestCase):
+    """Test Linear A98 RGB properties."""
+
+    def test_r(self):
+        """Test `r`."""
+
+        c = Color('color(--a98-rgb-linear 0.1 0.2 0.3)')
+        self.assertEqual(c['r'], 0.1)
+        c['r'] = 0.2
+        self.assertEqual(c['r'], 0.2)
+
+    def test_g(self):
+        """Test `g`."""
+
+        c = Color('color(--a98-rgb-linear 0.1 0.2 0.3)')
+        self.assertEqual(c['g'], 0.2)
+        c['g'] = 0.1
+        self.assertEqual(c['g'], 0.1)
+
+    def test_b(self):
+        """Test `b`."""
+
+        c = Color('color(--a98-rgb-linear 0.1 0.2 0.3)')
+        self.assertEqual(c['b'], 0.3)
+        c['b'] = 0.1
+        self.assertEqual(c['b'], 0.1)
+
+    def test_alpha(self):
+        """Test `alpha`."""
+
+        c = Color('color(--a98-rgb-linear 0.1 0.2 0.3)')
+        self.assertEqual(c['alpha'], 1)
+        c['alpha'] = 0.5
+        self.assertEqual(c['alpha'], 0.5)
diff --git a/tests/test_display_p3_linear.py b/tests/test_display_p3_linear.py
new file mode 100644
index 000000000..3c24b6599
--- /dev/null
+++ b/tests/test_display_p3_linear.py
@@ -0,0 +1,64 @@
+"""Test Linear Display P3."""
+import unittest
+from . import util
+from coloraide import Color
+import pytest
+
+
+class TestLinearDisplayP3(util.ColorAssertsPyTest):
+    """Test Linear Display P3."""
+
+    COLORS = [
+        ('red', 'color(--display-p3-linear 0.82246 0.03319 0.01708)'),
+        ('orange', 'color(--display-p3-linear 0.88926 0.39697 0.04432)'),
+        ('yellow', 'color(--display-p3-linear 1 1 0.08948)'),
+        ('green', 'color(--display-p3-linear 0.03832 0.2087 0.01563)'),
+        ('blue', 'color(--display-p3-linear 0 0 0.91052)'),
+        ('indigo', 'color(--display-p3-linear 0.05787 0.00234 0.20446)'),
+        ('violet', 'color(--display-p3-linear 0.74283 0.2442 0.80925)'),
+        ('white', 'color(--display-p3-linear 1 1 1)'),
+        ('gray', 'color(--display-p3-linear 0.21586 0.21586 0.21586)'),
+        ('black', 'color(--display-p3-linear 0 0 0)')
+    ]
+
+    @pytest.mark.parametrize('color1,color2', COLORS)
+    def test_colors(self, color1, color2):
+        """Test colors."""
+
+        self.assertColorEqual(Color(color1).convert('display-p3-linear'), Color(color2))
+
+
+class TestLinearDisplayP3Poperties(util.ColorAsserts, unittest.TestCase):
+    """Test Linear Display P3 properties."""
+
+    def test_r(self):
+        """Test `r`."""
+
+        c = Color('color(--display-p3-linear 0.1 0.2 0.3)')
+        self.assertEqual(c['r'], 0.1)
+        c['r'] = 0.2
+        self.assertEqual(c['r'], 0.2)
+
+    def test_g(self):
+        """Test `g`."""
+
+        c = Color('color(--display-p3-linear 0.1 0.2 0.3)')
+        self.assertEqual(c['g'], 0.2)
+        c['g'] = 0.1
+        self.assertEqual(c['g'], 0.1)
+
+    def test_b(self):
+        """Test `b`."""
+
+        c = Color('color(--display-p3-linear 0.1 0.2 0.3)')
+        self.assertEqual(c['b'], 0.3)
+        c['b'] = 0.1
+        self.assertEqual(c['b'], 0.1)
+
+    def test_alpha(self):
+        """Test `alpha`."""
+
+        c = Color('color(--display-p3-linear 0.1 0.2 0.3)')
+        self.assertEqual(c['alpha'], 1)
+        c['alpha'] = 0.5
+        self.assertEqual(c['alpha'], 0.5)
diff --git a/tests/test_prophoto_rgb_linear.py b/tests/test_prophoto_rgb_linear.py
new file mode 100644
index 000000000..5d62c00f9
--- /dev/null
+++ b/tests/test_prophoto_rgb_linear.py
@@ -0,0 +1,64 @@
+"""Test Linear ProPhoto RGB."""
+import unittest
+from . import util
+from coloraide import Color
+import pytest
+
+
+class TestLinearProPhotoRGB(util.ColorAssertsPyTest):
+    """Test Linear ProPhoto RGB."""
+
+    COLORS = [
+        ('red', 'color(--prophoto-rgb-linear 0.52928 0.09837 0.01688)'),
+        ('orange', 'color(--prophoto-rgb-linear 0.6535 0.42702 0.06115)'),
+        ('yellow', 'color(--prophoto-rgb-linear 0.85943 0.97183 0.13453)'),
+        ('green', 'color(--prophoto-rgb-linear 0.07127 0.18855 0.0254)'),
+        ('blue', 'color(--prophoto-rgb-linear 0.14057 0.02817 0.86547)'),
+        ('indigo', 'color(--prophoto-rgb-linear 0.06862 0.01321 0.19438)'),
+        ('violet', 'color(--prophoto-rgb-linear 0.64641 0.30317 0.78066)'),
+        ('white', 'color(--prophoto-rgb-linear 1 1 1)'),
+        ('gray', 'color(--prophoto-rgb-linear 0.21586 0.21586 0.21586)'),
+        ('black', 'color(--prophoto-rgb-linear 0 0 0)')
+    ]
+
+    @pytest.mark.parametrize('color1,color2', COLORS)
+    def test_colors(self, color1, color2):
+        """Test colors."""
+
+        self.assertColorEqual(Color(color1).convert('prophoto-rgb-linear'), Color(color2))
+
+
+class TestLinearProPhotoRGBPoperties(util.ColorAsserts, unittest.TestCase):
+    """Test Linear ProPhoto RGB properties."""
+
+    def test_r(self):
+        """Test `r`."""
+
+        c = Color('color(--prophoto-rgb-linear 0.1 0.2 0.3)')
+        self.assertEqual(c['r'], 0.1)
+        c['r'] = 0.2
+        self.assertEqual(c['r'], 0.2)
+
+    def test_g(self):
+        """Test `g`."""
+
+        c = Color('color(--prophoto-rgb-linear 0.1 0.2 0.3)')
+        self.assertEqual(c['g'], 0.2)
+        c['g'] = 0.1
+        self.assertEqual(c['g'], 0.1)
+
+    def test_b(self):
+        """Test `b`."""
+
+        c = Color('color(--prophoto-rgb-linear 0.1 0.2 0.3)')
+        self.assertEqual(c['b'], 0.3)
+        c['b'] = 0.1
+        self.assertEqual(c['b'], 0.1)
+
+    def test_alpha(self):
+        """Test `alpha`."""
+
+        c = Color('color(--prophoto-rgb-linear 0.1 0.2 0.3)')
+        self.assertEqual(c['alpha'], 1)
+        c['alpha'] = 0.5
+        self.assertEqual(c['alpha'], 0.5)
diff --git a/tests/test_rec2020_linear.py b/tests/test_rec2020_linear.py
new file mode 100644
index 000000000..943cba86c
--- /dev/null
+++ b/tests/test_rec2020_linear.py
@@ -0,0 +1,64 @@
+"""Test Linear Rec. 2020."""
+import unittest
+from . import util
+from coloraide import Color
+import pytest
+
+
+class TestLinearRec2020(util.ColorAssertsPyTest):
+    """Test Linear Rec. 2020."""
+
+    COLORS = [
+        ('red', 'color(--rec2020-linear 0.6274 0.0691 0.01639)'),
+        ('orange', 'color(--rec2020-linear 0.7513 0.41509 0.04951)'),
+        ('yellow', 'color(--rec2020-linear 0.95669 0.98864 0.1044)'),
+        ('green', 'color(--rec2020-linear 0.07108 0.19849 0.019)'),
+        ('blue', 'color(--rec2020-linear 0.04331 0.01136 0.8956)'),
+        ('indigo', 'color(--rec2020-linear 0.05381 0.0074 0.20108)'),
+        ('violet', 'color(--rec2020-linear 0.64696 0.27406 0.79939)'),
+        ('white', 'color(--rec2020-linear 1 1 1)'),
+        ('gray', 'color(--rec2020-linear 0.21586 0.21586 0.21586)'),
+        ('black', 'color(--rec2020-linear 0 0 0)')
+    ]
+
+    @pytest.mark.parametrize('color1,color2', COLORS)
+    def test_colors(self, color1, color2):
+        """Test colors."""
+
+        self.assertColorEqual(Color(color1).convert('rec2020-linear'), Color(color2))
+
+
+class TestLinearRec2020Poperties(util.ColorAsserts, unittest.TestCase):
+    """Test Linear Rec. 2020 properties."""
+
+    def test_r(self):
+        """Test `r`."""
+
+        c = Color('color(--rec2020-linear 0.1 0.2 0.3)')
+        self.assertEqual(c['r'], 0.1)
+        c['r'] = 0.2
+        self.assertEqual(c['r'], 0.2)
+
+    def test_g(self):
+        """Test `g`."""
+
+        c = Color('color(--rec2020-linear 0.1 0.2 0.3)')
+        self.assertEqual(c['g'], 0.2)
+        c['g'] = 0.1
+        self.assertEqual(c['g'], 0.1)
+
+    def test_b(self):
+        """Test `b`."""
+
+        c = Color('color(--rec2020-linear 0.1 0.2 0.3)')
+        self.assertEqual(c['b'], 0.3)
+        c['b'] = 0.1
+        self.assertEqual(c['b'], 0.1)
+
+    def test_alpha(self):
+        """Test `alpha`."""
+
+        c = Color('color(--rec2020-linear 0.1 0.2 0.3)')
+        self.assertEqual(c['alpha'], 1)
+        c['alpha'] = 0.5
+        self.assertEqual(c['alpha'], 0.5)
diff --git a/tests/test_rec2100pq.py b/tests/test_rec2100pq.py
new file mode 100644
index 000000000..36b05a835
--- /dev/null
+++ b/tests/test_rec2100pq.py
@@ -0,0 +1,64 @@
+"""Test Rec. 2100 PQ."""
+import unittest
+from . import util
+from coloraide import Color
+import pytest
+
+
+class TestRec2100PQ(util.ColorAssertsPyTest):
+    """Test Rec. 2100 PQ."""
+
+    COLORS = [
+        ('red', 'color(--rec2100pq 0.53255 0.32702 0.22007)'),
+        ('orange', 'color(--rec2100pq 0.55101 0.49099 0.30009)'),
+        ('yellow', 'color(--rec2100pq 0.57607 0.57949 0.36204)'),
+        ('green', 'color(--rec2100pq 0.32936 0.41997 0.2299)'),
+        ('blue', 'color(--rec2100pq 0.28965 0.19681 0.56919)'),
+        ('indigo', 'color(--rec2100pq 0.30671 0.17168 0.42117)'),
+        ('violet', 'color(--rec2100pq 0.53568 0.45048 0.55741)'),
+        ('white', 'color(--rec2100pq 0.58069 0.58069 0.58069)'),
+        ('gray', 'color(--rec2100pq 0.42781 0.42781 0.42781)'),
+        ('black', 'color(--rec2100pq 0 0 0)')
+    ]
+
+    @pytest.mark.parametrize('color1,color2', COLORS)
+    def test_colors(self, color1, color2):
+        """Test colors."""
+
+        self.assertColorEqual(Color(color1).convert('rec2100pq'), Color(color2))
+
+
+class TestRec2100PQPoperties(util.ColorAsserts, unittest.TestCase):
+    """Test Rec. 2100 PQ properties."""
+
+    def test_r(self):
+        """Test `r`."""
+
+        c = Color('color(--rec2100pq 0.1 0.2 0.3)')
+        self.assertEqual(c['r'], 0.1)
+        c['r'] = 0.2
+        self.assertEqual(c['r'], 0.2)
+
+    def test_g(self):
+        """Test `g`."""
+
+        c = Color('color(--rec2100pq 0.1 0.2 0.3)')
+        self.assertEqual(c['g'], 0.2)
+        c['g'] = 0.1
+        self.assertEqual(c['g'], 0.1)
+
+    def test_b(self):
+        """Test `b`."""
+
+        c = Color('color(--rec2100pq 0.1 0.2 0.3)')
+        self.assertEqual(c['b'], 0.3)
+        c['b'] = 0.1
+        self.assertEqual(c['b'], 0.1)
+
+    def test_alpha(self):
+        """Test `alpha`."""
+
+        c = Color('color(--rec2100pq 0.1 0.2 0.3)')
+        self.assertEqual(c['alpha'], 1)
+        c['alpha'] = 0.5
+        self.assertEqual(c['alpha'], 0.5)