0.12.0a8 fixed oklch -> rgb, it's linear_rgb, not sRGB

This commit is contained in:
Yusur 2025-12-31 19:27:34 +01:00
parent b27e18a3a0
commit 09ac75f07e
3 changed files with 49 additions and 35 deletions

View file

@ -35,16 +35,17 @@ from .strtools import PrefixIdentifier
from .validators import matches, not_less_than, not_greater_than, yesno from .validators import matches, not_less_than, not_greater_than, yesno
from .redact import redact_url_password from .redact import redact_url_password
from .http import WantsContentType from .http import WantsContentType
from .color import OKLabColor, chalk, WebColor, RGBColor, SRGBColor, XYZColor, OKLabColor from .color import OKLabColor, chalk, WebColor, RGBColor, LinearRGBColor, XYZColor, OKLabColor
from .mat import Matrix from .mat import Matrix
__version__ = "0.12.0a7" __version__ = "0.12.0a8"
__all__ = ( __all__ = (
'ConfigOptions', 'ConfigParserConfigSource', 'ConfigSource', 'ConfigValue', 'ConfigOptions', 'ConfigParserConfigSource', 'ConfigSource', 'ConfigValue',
'DictConfigSource', 'EnvConfigSource', 'I18n', 'Incomplete', 'JsonI18n', 'DictConfigSource', 'EnvConfigSource', 'I18n', 'Incomplete', 'JsonI18n',
'LinearRGBColor',
'Matrix', 'MissingConfigError', 'MissingConfigWarning', 'OKLabColor', 'Matrix', 'MissingConfigError', 'MissingConfigWarning', 'OKLabColor',
'PrefixIdentifier', 'RGBColor', 'SRGBColor', 'PrefixIdentifier', 'RGBColor',
'Siq', 'SiqCache', 'SiqGen', 'SiqType', 'Snowflake', 'SnowflakeGen', 'Siq', 'SiqCache', 'SiqGen', 'SiqType', 'Snowflake', 'SnowflakeGen',
'StringCase', 'TimedDict', 'TomlI18n', 'UserSigner', 'Wanted', 'WantsContentType', 'StringCase', 'TimedDict', 'TomlI18n', 'UserSigner', 'Wanted', 'WantsContentType',
'WebColor', 'XYZColor', 'WebColor', 'XYZColor',

View file

@ -99,12 +99,10 @@ chalk = Chalk()
class RGBColor(namedtuple('_WebColor', 'red green blue')): class RGBColor(namedtuple('_WebColor', 'red green blue')):
""" """
Representation of a color in the RGB TrueColor space. Representation of a color in the sRGB / TrueColor (0 to 255) space.
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.
""" """
@ -145,48 +143,40 @@ class RGBColor(namedtuple('_WebColor', 'red green blue')):
(self.blue + other.blue) // 2 (self.blue + other.blue) // 2
) )
def to_srgb(self): def to_linear_rgb(self):
""" """
Convert to sRGB space. Convert to linear RGB space.
*New in 0.12.0* *New in 0.12.0*
""" """
return SRGBColor(*( return LinearRGBColor(*(
(i / 12.92 if abs(i) <= 0.04045 else (i / 12.92 if abs(i / 255) <= 0.04045 else
(-1 if i < 0 else 1) * (((abs(i) + 0.55)) / 1.055) ** 2.4) for i in self (-1 if i < 0 else 1) * (((abs(i / 255) + 0.55)) / 1.055) ** 2.4) for i in self
)) ))
def to_oklab(self): def to_oklab(self):
return self.to_xyz().to_oklab() return self.to_linear_rgb().to_oklab()
__add__ = blend_with __add__ = blend_with
def __str__(self): def __str__(self):
return f"rgb({self.red}, {self.green}, {self.blue})" return f"rgb({self.red}, {self.green}, {self.blue})"
RGB_TO_XYZ = Matrix([
[0.41239079926595934, 0.357584339383878, 0.1804807884018343],
[0.21263900587151027, 0.715168678767756, 0.07219231536073371],
[0.01933081871559182, 0.11919477979462598, 0.9505321522496607]
])
def to_xyz(self): def to_xyz(self):
return XYZColor(*(self.RGB_TO_XYZ @ Matrix.as_column([x / 255 for x in self])).get_column()) return self.to_linear_rgb().to_xyz()
def to_oklch(self): def to_oklch(self):
return self.to_xyz().to_oklch() return self.to_linear_rgb().to_oklch()
def to_oklab(self):
return self.to_xyz().to_oklab()
WebColor = RGBColor WebColor = RGBColor
## The following have been adapted from ## The following have been adapted from
## https://gist.github.com/dkaraush/65d19d61396f5f3cd8ba7d1b4b3c9432 ## https://gist.github.com/dkaraush/65d19d61396f5f3cd8ba7d1b4b3c9432
class SRGBColor(namedtuple('_SRGBColor', 'red green blue')): class LinearRGBColor(namedtuple('_LRGBColor', 'red green blue')):
""" """
Represent a color in the sRGB space. Represent a color in the linear RGB space.
*New in 0.12.0* *New in 0.12.0*
""" """
@ -197,18 +187,27 @@ class SRGBColor(namedtuple('_SRGBColor', 'red green blue')):
def __str__(self): def __str__(self):
r, g, b = round(self.red, 4), round(self.green, 4), round(self.blue, 4) r, g, b = round(self.red, 4), round(self.green, 4), round(self.blue, 4)
return f"srgb({r}, {g}, {b})" return f"linear-rgb({r}, {g}, {b})"
def to_rgb(self): def to_rgb(self):
return RGBColor(*( return RGBColor(*(
((-1 if i < 0 else 1) * (1.055 * (abs(i) ** (1/2.4)) - 0.055) ((-1 if i < 0 else 1) * int(255 * (1.055 * (abs(i) ** (1/2.4)) - 0.055))
if abs(i) > 0.0031308 else 12.92 * i) for i in self)) if abs(i) > 0.0031308 else int(255 * (12.92 * i))) for i in self))
LRGB_TO_XYZ = Matrix([
[0.41239079926595934, 0.357584339383878, 0.1804807884018343],
[0.21263900587151027, 0.715168678767756, 0.07219231536073371],
[0.01933081871559182, 0.11919477979462598, 0.9505321522496607]
])
def to_xyz(self): def to_xyz(self):
return self.to_rgb().to_xyz() return XYZColor(*(self.LRGB_TO_XYZ @ Matrix.as_column([x for x in self])).get_column())
def to_oklab(self): def to_oklab(self):
return self.to_rgb().to_oklab() return self.to_xyz().to_oklab()
def to_oklch(self):
return self.to_xyz().to_oklch()
class XYZColor(namedtuple('_XYZColor', 'x y z')): class XYZColor(namedtuple('_XYZColor', 'x y z')):
@ -218,7 +217,7 @@ class XYZColor(namedtuple('_XYZColor', 'x y z')):
*New in 0.12.0* *New in 0.12.0*
""" """
XYZ_TO_RGB = Matrix([ XYZ_TO_LRGB = Matrix([
[ 3.2409699419045226, -1.537383177570094, -0.4986107602930034], [ 3.2409699419045226, -1.537383177570094, -0.4986107602930034],
[-0.9692436362808796, 1.8759675015077202, 0.04155505740717559], [-0.9692436362808796, 1.8759675015077202, 0.04155505740717559],
[ 0.05563007969699366, -0.20397695888897652, 1.0569715142428786] [ 0.05563007969699366, -0.20397695888897652, 1.0569715142428786]
@ -236,9 +235,12 @@ class XYZColor(namedtuple('_XYZColor', 'x y z')):
[0.0259040424655478, 0.7827717124575296, -0.8086757549230774] [0.0259040424655478, 0.7827717124575296, -0.8086757549230774]
]) ])
def to_rgb(self): def to_linear_rgb(self):
return RGBColor(*[int(x * 255) for x in (self.XYZ_TO_RGB @ Matrix.as_column(self)).get_column()]) return LinearRGBColor(*[x for x in (self.XYZ_TO_LRGB @ Matrix.as_column(self)).get_column()])
def to_rgb(self):
return self.to_linear_rgb().to_rgb()
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]
@ -291,6 +293,9 @@ class OKLabColor(namedtuple('_OKLabColor', 'l a b')):
return f'oklab({l} {c} {h})' return f'oklab({l} {c} {h})'
def to_linear_rgb(self):
return self.to_xyz().to_linear_rgb()
def to_rgb(self): def to_rgb(self):
return self.to_xyz().to_rgb() return self.to_xyz().to_rgb()
@ -315,7 +320,11 @@ class OKLCHColor(namedtuple('_OKLCHColor', 'l c h')):
self.c * math.sin(self.h * math.pi / 180) self.c * math.sin(self.h * math.pi / 180)
) )
def to_xyz(self):
return self.to_oklab().to_xyz()
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', 'OKLCHColor')
__all__ = ('chalk', 'WebColor', "RGBColor", 'LinearRGBColor', 'XYZColor', 'OKLabColor', 'OKLCHColor')

View file

@ -30,4 +30,8 @@ class TestColor(unittest.TestCase):
def test_oklch_to_rgb(self): 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.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.7653, 0.1306, 194.77).to_rgb(), RGBColor(0, 204, 204))
self.assertEqual(OKLCHColor(0.5931, 0., 0.).to_rgb(), RGBColor(126, 126, 126)) self.assertEqual(OKLCHColor(0.5932, 0., 0.).to_rgb(), RGBColor(126, 126, 126))
def test_rgb_to_oklch(self):
self.assertEqual(RGBColor(222, 62, 45).to_oklch(), OKLCHColor(0.6,0.2, 30.))
self.assertEqual(RGBColor(156, 123, 49).to_oklch(), OKLCHColor(.6, .1, 85.))