Color Class
class Color:
# Color instance creation:
def __init__(self, r: float = 0, g: float = 0, b: float = 0, a: float = 1) -> None:
self.r = r
self.g = g
self.b = b
self.a = a
# Color string representation: e.g. 'rgba(255,165,0,1)'
def to_string(self) -> str:
return f"rgba({self.r}, {self.g}, {self.b}, {self.a})"
def to_rgb_string(self) -> str:
return f"rgb({self.r}, {self.g}, {self.b})"
def __str__(self) -> str:
return self.to_string()
@staticmethod
def from_string(string: str) -> "Color":
components = re.split(r"[\(),]", string)
channels = (components[1:5]
if string.startswith("rgba") else components[0:4])
return Color(*map(int, channels))
# Color copying:
def copy(self) -> "Color":
return Color(
self.r, self.g, self.b, self.a
)
# Color comparison:
def eq(self, other: "Color", observe_alpha = False) -> bool:
"""=="""
return (isinstance(other, Color) and
self.r == other.r and
self.g == other.g and
self.b == other.b and
(self.a == other.a if observe_alpha else True))
def __eq__(self, other) -> bool:
if isinstance(other, Color):
return self.eq(other)
return NotImplemented
def is_close_to(self, other: "Color", observe_alpha = False, tolerance = 10) -> bool:
"""
experimental - answer whether a color is "close" to another one by
a given percentage. tolerance is the percentage by which each color
channel may diverge, alpha needs to be the exact same unless ignored
"""
thres = 2.55 * tolerance
def dist(a, b):
diff = a - b
return 255 + diff if diff < 0 else diff
return (isinstance(other, Color) and
dist(self.r, other.r) < thres and
dist(self.g, other.g) < thres and
dist(self.b, other.b) < thres and
(self.a == other.a if observe_alpha else True))
# Color conversion (hsv):
def hsv(self) -> tuple[float, float, float]:
"""ignores alpha"""
rr = self.r / 255
gg = self.g / 255
bb = self.b / 255
h, s, v = colorsys.rgb_to_hsv(rr, gg, bb)
return h, s, v
def set_hsv(self, h, s, v) -> None:
"""ignores alpha, h, s and v are to be within [0, 1]"""
rr, gg, bb = colorsys.hsv_to_rgb(h, s, v)
self.r = rr * 255
self.g = gg * 255
self.b = bb * 255
# Color conversion (hsl):
def hsl(self) -> tuple[float, float, float]:
"""ignores alpha"""
rr = self.r / 255
gg = self.g / 255
bb = self.b / 255
h, l, s = colorsys.rgb_to_hls(rr, gg, bb)
return h, s, l
def set_hsl(self, h, s, l) -> None:
"""ignores alpha, h, s and l are to be within [0, 1]"""
rr, gg, bb = colorsys.hls_to_rgb(h, l, s)
self.r = rr * 255
self.g = gg * 255
self.b = bb * 255
# Color mixing:
def mixed(self, proportion: float, other: "Color") -> "Color":
"""answer a copy of this color mixed with another color, ignore alpha"""
frac1 = min(max(proportion, 0), 1)
frac2 = 1 - frac1
return Color(
self.r * frac1 + other.r * frac2,
self.g * frac1 + other.g * frac2,
self.b * frac1 + other.b * frac2
)
def darker(self, percent: Optional[float]) -> "Color":
"""return an rgb-interpolated darker copy of me, ignore alpha"""
fract = 0.8333
if percent is not None:
fract = (100 - percent) / 100
return self.mixed(fract, BLACK)
def lighter(self, percent: Optional[float]) -> "Color":
"""return an rgb-interpolated lighter copy of me, ignore alpha"""
fract = 0.8333
if percent is not None:
fract = (100 - percent) / 100
return self.mixed(fract, WHITE)
def dansDarker(self) -> "Color":
"""return an hsv-interpolated darker copy of me, ignore alpha"""
hsv = self.hsv()
result = Color()
vv = max(hsv[2] - 0.16, 0)
result.set_hsv(hsv[0], hsv[1], vv)
return result
def inverted(self) -> "Color":
return Color(
255 - self.r,
255 - self.g,
255 - self.b
)
def solid(self) -> "Color":
return Color(
self.r,
self.g,
self.b
)