0.12.0a7 partly fixed OKLab->OKLCH conversion, added usability warnings

This commit is contained in:
Yusur 2025-12-31 10:32:02 +01:00
parent 769d37f83a
commit b27e18a3a0
3 changed files with 32 additions and 12 deletions

View file

@ -38,7 +38,7 @@ from .http import WantsContentType
from .color import OKLabColor, chalk, WebColor, RGBColor, SRGBColor, XYZColor, OKLabColor from .color import OKLabColor, chalk, WebColor, RGBColor, SRGBColor, XYZColor, OKLabColor
from .mat import Matrix from .mat import Matrix
__version__ = "0.12.0a6" __version__ = "0.12.0a7"
__all__ = ( __all__ = (
'ConfigOptions', 'ConfigParserConfigSource', 'ConfigSource', 'ConfigValue', 'ConfigOptions', 'ConfigParserConfigSource', 'ConfigSource', 'ConfigValue',

View file

@ -23,6 +23,7 @@ from collections import namedtuple
from functools import lru_cache from functools import lru_cache
import math import math
from suou.functools import deprecated
from suou.mat import Matrix from suou.mat import Matrix
@ -102,6 +103,8 @@ class RGBColor(namedtuple('_WebColor', 'red green blue')):
Useful for theming. Useful for theming.
XXX CURRENTLY THE OKLCH CONVERSION DOES NOT WORK
*Changed in 0.12.0*: name is now RGBColor, with WebColor being an alias. *Changed in 0.12.0*: name is now RGBColor, with WebColor being an alias.
Added conversions to and from OKLCH, OKLab, sRGB, and XYZ. Added conversions to and from OKLCH, OKLab, sRGB, and XYZ.
""" """
@ -168,7 +171,7 @@ class RGBColor(namedtuple('_WebColor', 'red green blue')):
]) ])
def to_xyz(self): def to_xyz(self):
return XYZColor(*(self.RGB_TO_XYZ @ Matrix.as_column(self)).get_column()) return XYZColor(*(self.RGB_TO_XYZ @ Matrix.as_column([x / 255 for x in self])).get_column())
def to_oklch(self): def to_oklch(self):
return self.to_xyz().to_oklch() return self.to_xyz().to_oklch()
@ -192,7 +195,9 @@ class SRGBColor(namedtuple('_SRGBColor', 'red green blue')):
blue: float blue: float
def __str__(self): def __str__(self):
return f"srgb({self.red}, {self.green}, {self.blue})" r, g, b = round(self.red, 4), round(self.green, 4), round(self.blue, 4)
return f"srgb({r}, {g}, {b})"
def to_rgb(self): def to_rgb(self):
return RGBColor(*( return RGBColor(*(
@ -232,8 +237,8 @@ class XYZColor(namedtuple('_XYZColor', 'x y z')):
]) ])
def to_rgb(self): def to_rgb(self):
return RGBColor(*(self.XYZ_TO_RGB @ Matrix.as_column(self)).get_column()) return RGBColor(*[int(x * 255) for x in (self.XYZ_TO_RGB @ Matrix.as_column(self)).get_column()])
def to_oklab(self): def to_oklab(self):
lms = (self.XYZ_TO_LMS @ Matrix.as_column(self)).get_column() lms = (self.XYZ_TO_LMS @ Matrix.as_column(self)).get_column()
lmsg = [math.cbrt(i) for i in lms] lmsg = [math.cbrt(i) for i in lms]
@ -243,6 +248,11 @@ class XYZColor(namedtuple('_XYZColor', 'x y z')):
def to_oklch(self): def to_oklch(self):
return self.to_oklab().to_oklch() return self.to_oklab().to_oklch()
def __str__(self):
x, y, z = round(self.x, 4), round(self.y, 4), round(self.z, 4)
return f'xyz({x} {y} {z})'
class OKLabColor(namedtuple('_OKLabColor', 'l a b')): class OKLabColor(namedtuple('_OKLabColor', 'l a b')):
""" """
@ -276,6 +286,11 @@ class OKLabColor(namedtuple('_OKLabColor', 'l a b')):
0 if abs(self.a) < .0002 and abs(self.b) < .0002 else (((math.atan2(self.b, self.a) * 180) / math.pi % 360) + 360) % 360 0 if abs(self.a) < .0002 and abs(self.b) < .0002 else (((math.atan2(self.b, self.a) * 180) / math.pi % 360) + 360) % 360
) )
def __str__(self):
l, c, h = round(self.l, 4), round(self.a, 4), round(self.b, 4)
return f'oklab({l} {c} {h})'
def to_rgb(self): def to_rgb(self):
return self.to_xyz().to_rgb() return self.to_xyz().to_rgb()
@ -289,19 +304,18 @@ class OKLCHColor(namedtuple('_OKLCHColor', 'l c h')):
""" """
def __str__(self): def __str__(self):
l, c, h = round(self.l, 4), round(self.c, 4), round(self.h, 4) l, c, h = round(self.l, 4), round(self.c, 4), round(self.h, 2)
return f'oklch({l}, {c}, {h})'
return f'oklch({l} {c} {h})'
def to_oklab(self): def to_oklab(self):
return OKLabColor( return OKLabColor(
self.l, self.l,
self.c * math.cos(self.h * math.pi / 180), self.c * math.cos(self.h * math.pi / 180),
self.h * math.cos(self.h * math.pi / 180) self.c * math.sin(self.h * math.pi / 180)
) )
def to_rgb(self): def to_rgb(self):
return self.to_oklab().to_rgb() return self.to_oklab().to_rgb()
__all__ = ('chalk', 'WebColor', "RGBColor", 'SRGBColor', 'XYZColor', 'OKLabColor') __all__ = ('chalk', 'WebColor', "RGBColor", 'SRGBColor', 'XYZColor', 'OKLabColor', 'OKLCHColor')

View file

@ -2,7 +2,8 @@
import unittest import unittest
from suou import chalk from suou import RGBColor, chalk
from suou.color import OKLCHColor
class TestColor(unittest.TestCase): class TestColor(unittest.TestCase):
def setUp(self) -> None: def setUp(self) -> None:
@ -24,4 +25,9 @@ class TestColor(unittest.TestCase):
strg = "The quick brown fox jumps over the lazy dog" strg = "The quick brown fox jumps over the lazy dog"
self.assertEqual(f'\x1b[1m{strg}\x1b[22m', chalk.bold(strg)) self.assertEqual(f'\x1b[1m{strg}\x1b[22m', chalk.bold(strg))
self.assertEqual(f'\x1b[2m{strg}\x1b[22m', chalk.faint(strg)) self.assertEqual(f'\x1b[2m{strg}\x1b[22m', chalk.faint(strg))
self.assertEqual(f'\x1b[1m\x1b[33m{strg}\x1b[39m\x1b[22m', chalk.bold.yellow(strg)) self.assertEqual(f'\x1b[1m\x1b[33m{strg}\x1b[39m\x1b[22m', chalk.bold.yellow(strg))
def test_oklch_to_rgb(self):
self.assertEqual(OKLCHColor(0.628, 0.2577, 29.23).to_rgb(), RGBColor(255, 0, 0))
self.assertEqual(OKLCHColor(0.7653, 0.1306, 194.77).to_rgb(), RGBColor(0, 204, 204))
self.assertEqual(OKLCHColor(0.5931, 0., 0.).to_rgb(), RGBColor(126, 126, 126))