mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
Merge pull request #360 from rodrigogiraoserrao/color-palette
Add conversion to/from the CIE-L*ab color space.
This commit is contained in:
@@ -33,6 +33,14 @@ class HSV(NamedTuple):
|
||||
v: float
|
||||
|
||||
|
||||
class Lab(NamedTuple):
|
||||
"""A color in CIE-L*ab format."""
|
||||
|
||||
L: float
|
||||
a: float
|
||||
b: float
|
||||
|
||||
|
||||
RE_COLOR = re.compile(
|
||||
r"""^
|
||||
\#([0-9a-fA-F]{6})$|
|
||||
@@ -367,6 +375,60 @@ class ColorPair(NamedTuple):
|
||||
)
|
||||
|
||||
|
||||
def rgb_to_lab(rgb: Color) -> Lab:
|
||||
"""Convert an RGB color to the CIE-L*ab format.
|
||||
|
||||
Uses the standard RGB color space with a D65/2⁰ standard illuminant.
|
||||
Conversion passes through the XYZ color space.
|
||||
Cf. http://www.easyrgb.com/en/math.php.
|
||||
"""
|
||||
|
||||
r, g, b = rgb.r / 255, rgb.g / 255, rgb.b / 255
|
||||
|
||||
r = pow((r + 0.055) / 1.055, 2.4) if r > 0.04045 else r / 12.92
|
||||
g = pow((g + 0.055) / 1.055, 2.4) if g > 0.04045 else g / 12.92
|
||||
b = pow((b + 0.055) / 1.055, 2.4) if b > 0.04045 else b / 12.92
|
||||
|
||||
x = (r * 41.24 + g * 35.76 + b * 18.05) / 95.047
|
||||
y = (r * 21.26 + g * 71.52 + b * 7.22) / 100
|
||||
z = (r * 1.93 + g * 11.92 + b * 95.05) / 108.883
|
||||
|
||||
off = 16 / 116
|
||||
x = pow(x, 1 / 3) if x > 0.008856 else 7.787 * x + off
|
||||
y = pow(y, 1 / 3) if y > 0.008856 else 7.787 * y + off
|
||||
z = pow(z, 1 / 3) if z > 0.008856 else 7.787 * z + off
|
||||
|
||||
return Lab(116 * y - 16, 500 * (x - y), 200 * (y - z))
|
||||
|
||||
|
||||
def lab_to_rgb(lab: Lab) -> Color:
|
||||
"""Convert a CIE-L*ab color to RGB.
|
||||
|
||||
Uses the standard RGB color space with a D65/2⁰ standard illuminant.
|
||||
Conversion passes through the XYZ color space.
|
||||
Cf. http://www.easyrgb.com/en/math.php.
|
||||
"""
|
||||
|
||||
y = (lab.L + 16) / 116
|
||||
x = lab.a / 500 + y
|
||||
z = y - lab.b / 200
|
||||
|
||||
off = 16 / 116
|
||||
y = pow(y, 3) if y > 0.2068930344 else (y - off) / 7.787
|
||||
x = 0.95047 * pow(x, 3) if x > 0.2068930344 else 0.122059 * (x - off)
|
||||
z = 1.08883 * pow(z, 3) if z > 0.2068930344 else 0.139827 * (z - off)
|
||||
|
||||
r = x * 3.2406 + y * -1.5372 + z * -0.4986
|
||||
g = x * -0.9689 + y * 1.8758 + z * 0.0415
|
||||
b = x * 0.0557 + y * -0.2040 + z * 1.0570
|
||||
|
||||
r = 1.055 * pow(r, 1 / 2.4) - 0.055 if r > 0.0031308 else 12.92 * r
|
||||
g = 1.055 * pow(g, 1 / 2.4) - 0.055 if g > 0.0031308 else 12.92 * g
|
||||
b = 1.055 * pow(b, 1 / 2.4) - 0.055 if b > 0.0031308 else 12.92 * b
|
||||
|
||||
return Color(int(r * 255), int(g * 255), int(b * 255))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
from rich import print
|
||||
|
||||
@@ -3,7 +3,7 @@ import pytest
|
||||
from rich.color import Color as RichColor
|
||||
from rich.text import Text
|
||||
|
||||
from textual.color import Color, ColorPair
|
||||
from textual.color import Color, ColorPair, Lab, lab_to_rgb, rgb_to_lab
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -76,3 +76,52 @@ def test_hls():
|
||||
assert red.hls == pytest.approx(
|
||||
(0.9888888888888889, 0.43137254901960786, 0.818181818181818)
|
||||
)
|
||||
|
||||
|
||||
# Computed with http://www.easyrgb.com/en/convert.php,
|
||||
# (r, g, b) values in sRGB to (L*, a*, b*) values in CIE-L*ab.
|
||||
RGB_LAB_DATA = [
|
||||
(10, 23, 73, 10.245, 15.913, -32.672),
|
||||
(200, 34, 123, 45.438, 67.750, -8.008),
|
||||
(0, 0, 0, 0, 0, 0),
|
||||
(255, 255, 255, 100, 0, 0),
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"r, g, b, L_, a_, b_",
|
||||
RGB_LAB_DATA,
|
||||
)
|
||||
def test_rgb_to_lab(r, g, b, L_, a_, b_):
|
||||
"""Test conversion from the RGB color space to CIE-L*ab."""
|
||||
rgb = Color(r, g, b)
|
||||
lab = rgb_to_lab(rgb)
|
||||
assert lab.L == pytest.approx(L_, abs=0.1)
|
||||
assert lab.a == pytest.approx(a_, abs=0.1)
|
||||
assert lab.b == pytest.approx(b_, abs=0.1)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"r, g, b, L_, a_, b_",
|
||||
RGB_LAB_DATA,
|
||||
)
|
||||
def test_lab_to_rgb(r, g, b, L_, a_, b_):
|
||||
"""Test conversion from the CIE-L*ab color space to RGB."""
|
||||
|
||||
lab = Lab(L_, a_, b_)
|
||||
rgb = lab_to_rgb(lab)
|
||||
assert rgb.r == pytest.approx(r, abs=1)
|
||||
assert rgb.g == pytest.approx(g, abs=1)
|
||||
assert rgb.b == pytest.approx(b, abs=1)
|
||||
|
||||
|
||||
def test_rgb_lab_rgb_roundtrip():
|
||||
"""Test RGB -> CIE-L*ab -> RGB color conversion roundtripping."""
|
||||
|
||||
for r in range(0, 256, 4):
|
||||
for g in range(0, 256, 4):
|
||||
for b in range(0, 256, 4):
|
||||
c_ = lab_to_rgb(rgb_to_lab(Color(r, g, b)))
|
||||
assert c_.r == pytest.approx(r, abs=1)
|
||||
assert c_.g == pytest.approx(g, abs=1)
|
||||
assert c_.b == pytest.approx(b, abs=1)
|
||||
|
||||
Reference in New Issue
Block a user