feat(color): add HSV support to Color class

Add `Color.hsv` property and `Color.from_hsv` class method.

This utilizes the existing `HSV` namedtuple which is currently unused.
This commit is contained in:
TomJGooding
2025-05-15 14:27:58 +01:00
parent e9be1898ca
commit a9e84b8cf6
2 changed files with 41 additions and 2 deletions

View File

@@ -31,7 +31,7 @@ output = table
from __future__ import annotations
import re
from colorsys import hls_to_rgb, rgb_to_hls
from colorsys import hls_to_rgb, hsv_to_rgb, rgb_to_hls, rgb_to_hsv
from functools import lru_cache
from operator import itemgetter
from typing import Callable, NamedTuple
@@ -82,7 +82,7 @@ class HSV(NamedTuple):
s: float
"""Saturation in range 0 to 1."""
v: float
"""Value un range 0 to 1."""
"""Value in range 0 to 1."""
class Lab(NamedTuple):
@@ -212,6 +212,21 @@ class Color(NamedTuple):
r, g, b = hls_to_rgb(h, l, s)
return cls(int(r * 255 + 0.5), int(g * 255 + 0.5), int(b * 255 + 0.5))
@classmethod
def from_hsv(cls, h: float, s: float, v: float) -> Color:
"""Create a color from HSV components.
Args:
h: Hue.
s: Saturation.
v: Value.
Returns:
A new color.
"""
r, g, b = hsv_to_rgb(h, s, v)
return cls(int(r * 255 + 0.5), int(g * 255 + 0.5), int(b * 255 + 0.5))
@property
def inverse(self) -> Color:
"""The inverse of this color.
@@ -286,6 +301,19 @@ class Color(NamedTuple):
h, l, s = rgb_to_hls(r, g, b)
return HSL(h, s, l)
@property
def hsv(self) -> HSV:
"""This color in HSV format.
HSV color is an alternative way of representing a color, which can be used in certain color calculations.
Returns:
Color encoded in HSV format.
"""
r, g, b = self.normalized
h, s, v = rgb_to_hsv(r, g, b)
return HSV(h, s, v)
@property
def brightness(self) -> float:
"""The human perceptual brightness.

View File

@@ -52,6 +52,17 @@ def test_hsl():
assert red.hsl.css == "hsl(356,81.8%,43.1%)"
def test_hsv():
red = Color(200, 20, 32)
print(red.hsv)
assert red.hsv == pytest.approx(
(0.9888888888888889, 0.8999999999999999, 0.7843137254901961)
)
assert Color.from_hsv(
0.9888888888888889, 0.8999999999999999, 0.7843137254901961
).normalized == pytest.approx(red.normalized, rel=1e-5)
def test_color_brightness():
assert Color(255, 255, 255).brightness == 1
assert Color(0, 0, 0).brightness == 0