aboutsummaryrefslogtreecommitdiff
path: root/thcolor/colors.py
diff options
context:
space:
mode:
Diffstat (limited to 'thcolor/colors.py')
-rw-r--r--thcolor/colors.py175
1 files changed, 145 insertions, 30 deletions
diff --git a/thcolor/colors.py b/thcolor/colors.py
index d049871..2be1985 100644
--- a/thcolor/colors.py
+++ b/thcolor/colors.py
@@ -9,7 +9,7 @@ from math import (
atan2 as _atan2, ceil as _ceil, cos as _cos, sin as _sin, sqrt as _sqrt,
)
from typing import (
- Any as _Any, Optional as _Optional, Tuple as _Tuple, Sequence as _Sequence,
+ Any as _Any, Optional as _Optional, Sequence as _Sequence, Tuple as _Tuple,
)
from .angles import (
@@ -335,25 +335,16 @@ class SRGBColor(Color):
red = float(red)
except (TypeError, ValueError):
raise ValueError(f'red should be a float, is {red!r}')
- else:
- if red < 0 or red > 1:
- raise ValueError('red should be between 0.0 and 1.0')
try:
green = float(green)
except (TypeError, ValueError):
raise ValueError(f'green should be a float, is {green!r}')
- else:
- if green < 0 or green > 1:
- raise ValueError('green should be between 0.0 and 1.0')
try:
blue = float(blue)
except (TypeError, ValueError):
raise ValueError(f'blue should be a float, is {blue!r}')
- else:
- if blue < 0 or blue > 1:
- raise ValueError('blue should be between 0.0 and 1.0')
self._red = red
self._green = green
@@ -594,20 +585,14 @@ class SRGBColor(Color):
""" Get an YIQColor out of the current object. """
r, g, b = self.red, self.green, self.blue
- y = .3 * r + .59 * g + .11 * b
return YIQColor(
- y=y,
- i=.74 * (r - y) - .27 * (b - y),
- q=.48 * (r - y) + .41 * (b - y),
+ y=.587 * g + .114 * b + .299 * r,
+ i=-.275 * g - .321 * b + .596 * r,
+ q=-.523 * g + .311 * b + .212 * r,
alpha=self.alpha,
)
- def asyuv(self) -> 'YUVColor':
- """ Get an YUVColor out of the current object. """
-
- raise NotImplementedError # TODO
-
def asbytes(self) -> _Tuple[int, int, int]:
""" Get the red, blue and green bytes. """
@@ -639,6 +624,27 @@ class HSLColor(Color):
):
super().__init__(alpha)
+ if not isinstance(hue, _Angle):
+ raise ValueError('hue should be an angle')
+
+ try:
+ saturation = float(saturation)
+ except (TypeError, ValueError):
+ raise ValueError(
+ f'saturation should be a float, is {saturation!r}',
+ )
+ else:
+ if saturation < 0 or saturation > 1:
+ raise ValueError('saturation should be between 0.0 and 1.0')
+
+ try:
+ lightness = float(lightness)
+ except (TypeError, ValueError):
+ raise ValueError(f'lightness should be a float, is {lightness!r}')
+ else:
+ if lightness < 0 or lightness > 1:
+ raise ValueError('lightness should be between 0.0 and 1.0')
+
self._hue = hue
self._saturation = saturation
self._lightness = lightness
@@ -953,7 +959,7 @@ class CMYKColor(Color):
def assrgb(self) -> 'SRGBColor':
""" Get an SRGBColor out of the current object. """
- c, m, y, k, a = self
+ c, m, y, k = self.cyan, self.magenta, self.yellow, self.black
r = 1 - min(1, c * (1 - k) + k)
g = 1 - min(1, m * (1 - k) + k)
@@ -963,7 +969,7 @@ class CMYKColor(Color):
red=r,
green=g,
blue=b,
- alpha=a,
+ alpha=self.alpha,
)
def ascmyk(self) -> 'CMYKColor':
@@ -1031,7 +1037,7 @@ class LABColor(Color):
def assrgb(self) -> 'SRGBColor':
""" Get an SRGBColor out of the current object. """
- raise NotImplementedError # TODO
+ return self.asxyz().assrgb()
def aslab(self) -> 'LABColor':
""" Get a LABColor out of the current object. """
@@ -1050,8 +1056,8 @@ class LABColor(Color):
return LCHColor(
lightness=l,
- chroma=_sqrt(a * a + b * b),
- hue=_RadiansAngle(_atan2(b, a)),
+ chroma=_sqrt(a ** 2 + b ** 2),
+ hue=_RadiansAngle(_atan2(b, a)).asprincipal(),
alpha=self.alpha,
)
@@ -1077,6 +1083,25 @@ class LCHColor(Color):
):
super().__init__(alpha)
+ try:
+ lightness = float(lightness)
+ except (TypeError, ValueError):
+ raise ValueError(f'lightness should be a float, is {lightness!r}')
+ else:
+ if lightness < 0 or lightness > 1:
+ raise ValueError('lightness should be between 0.0 and 1.0')
+
+ try:
+ chroma = float(chroma)
+ except (TypeError, ValueError):
+ raise ValueError(f'chroma should be a float, is {chroma!r}')
+ else:
+ if chroma < 0:
+ chroma = 0.0
+
+ if not isinstance(hue, _Angle):
+ raise TypeError(f'hue should be an Angle, is {hue!r}')
+
self._lightness = lightness
self._chroma = chroma
self._hue = hue
@@ -1109,6 +1134,11 @@ class LCHColor(Color):
return self._hue
+ def assrgb(self) -> 'SRGBColor':
+ """ Get an SRGBColor out of the current object. """
+
+ return self.aslab().asxyz().assrgb()
+
def aslab(self) -> 'LABColor':
""" Get a LABColor out of the current object. """
@@ -1147,6 +1177,30 @@ class XYZColor(Color):
def __init__(self, x: float, y: float, z: float, alpha: float = 1.0):
super().__init__(alpha)
+ try:
+ x = float(x)
+ except (TypeError, ValueError):
+ raise ValueError(f'x should be a float, is {x!r}')
+ else:
+ if x < 0 or x > 1:
+ raise ValueError('x should be between 0.0 and 1.0')
+
+ try:
+ y = float(y)
+ except (TypeError, ValueError):
+ raise ValueError(f'y should be a float, is {y!r}')
+ else:
+ if y < 0 or y > 1:
+ raise ValueError('y should be between 0.0 and 1.0')
+
+ try:
+ z = float(z)
+ except (TypeError, ValueError):
+ raise ValueError(f'z should be a float, is {z!r}')
+ else:
+ if z < 0 or z > 1:
+ raise ValueError('z should be between 0.0 and 1.0')
+
self._x = x
self._y = y
self._z = z
@@ -1172,10 +1226,76 @@ class XYZColor(Color):
return self._z
+ def assrgb(self) -> 'SRGBColor':
+ """ Get an SRGBColor out of the current object. """
+
+ # For more information about this algorithm, see these links:
+ #
+ # * http://www.easyrgb.com/en/math.php#text9
+ # (insufficient precision but you get the gist of the algorithm).
+ # * https://stackoverflow.com/a/45238704
+
+ x, y, z = self.x, self.y, self.z
+
+ r = x * 3.2404542 + y * -1.5371385 + z * -.4985314
+ g = x * -.9692660 + y * 1.8760108 + z * .0415560
+ b = x * .0556434 + y * -.2040259 + z * 1.0572252
+
+ r, g, b = map(
+ lambda x: (
+ 1.055 * (x ** (1 / 2.4)) - .055
+ if x > .0031308
+ else 12.92 * x
+ ),
+ (r, g, b),
+ )
+
+ return SRGBColor(
+ red=r,
+ green=g,
+ blue=b,
+ alpha=self.alpha,
+ )
+
+ def aslab(self) -> 'LABColor':
+ """ Get a LABColor out of the current object. """
+
+ x, y, z = self.x, self.y, self.z
+
+ # Uses the current industry standard formula, delta E 2000,
+ # on D65/2°.
+ # For more information, see the following links:
+ #
+ # * http://www.easyrgb.com/en/math.php#text9
+ # * https://github.com/cangoektas/xyz-to-lab/blob/master/src/index.js
+
+ D65 = (95.047, 100, 108.883)
+
+ def calculate(data):
+ value, ref = data
+ value = value * 100 / ref
+ if value > .008856:
+ return value ** (1 / 3)
+ return value * 7.878 + 16 / 116
+
+ x, y, z = map(calculate, zip((x, y, z), D65))
+
+ return LABColor(
+ lightness=(116 * y - 16) / 100,
+ a=500 * (x - y),
+ b=200 * (y - z),
+ alpha=self.alpha,
+ )
+
def asxyz(self) -> 'XYZColor':
""" Get an XYZColor out of the current object. """
- return self
+ return XYZColor(
+ x=self.x,
+ y=self.y,
+ z=self.z,
+ alpha=self.alpha,
+ )
class YIQColor(Color):
@@ -1299,11 +1419,6 @@ class YUVColor(Color):
return self._v
- def assrgb(self) -> 'SRGBColor':
- """ Get an SRGBColor out of the current object. """
-
- raise NotImplementedError # TODO
-
def asyuv(self) -> 'YUVColor':
""" Get an YUVColor out of the current object. """