diff --git a/colorhash/color.py b/colorhash/color.py
new file mode 100644
index 0000000..2248410
--- /dev/null
+++ b/colorhash/color.py
@@ -0,0 +1,72 @@
+import abc
+import colorsys
+import dataclasses
+
+
+class Color(metaclass=abc.ABCMeta):
+ """
+ An abstract color class.
+
+ This can be used to convert any color format into another color format.
+ """
+
+ def to_html_color(self) -> str:
+ """
+ Convert this color to an HTML color.
+
+ This may produce unexpected results if you are expecting an RGB color. If you are expecting
+ RGB, then you should use `color.to_rgb().to_html_color()` instead.
+ """
+
+ @abc.abstractmethod
+ def to_rgb(self) -> "RGBColor":
+ "Convert this color into an RGB color."
+
+ @abc.abstractmethod
+ def to_hsl(self) -> "HSLColor":
+ "Convert this color into an HSL color."
+
+
+@dataclasses.dataclass
+class RGBColor(Color):
+ """
+ An RGB color. Colors are expected to be a floating point value from [0.0-255.0).
+ """
+ r: float
+ g: float
+ b: float
+
+ def to_html_color(self) -> str:
+ r, g, b = round(self.r), round(self.g), round(self.b)
+ return f"#{r:02x}{g:02x}{b:02x}"
+
+ def to_rgb(self) -> "RGBColor":
+ return self
+
+ def to_hsl(self) -> "HSLColor":
+ r = self.r / 255.0
+ g = self.g / 255.0
+ b = self.b / 255.0
+ h, l, s = colorsys.rgb_to_hls(r, g, b)
+ return HSLColor(h * 360.0, s * 100.0, l * 100.0)
+
+
+@dataclasses.dataclass
+class HSLColor(Color):
+ h: float
+ s: float
+ l: float
+
+ def to_html_color(self) -> str:
+ return f"hsl({self.h:.02f},{self.s:.02f}%,{self.l:.02f}%)"
+
+ def to_rgb(self) -> "RGBColor":
+ h = self.h / 360.0
+ s = self.s / 100.0
+ l = self.l / 100.0
+ r, g, b = colorsys.hls_to_rgb(h, l, s)
+ return RGBColor(r * 255.0, g * 255.0, b * 255.0)
+
+
+ def to_hsl(self) -> "HSLColor":
+ return self
diff --git a/colorhash/colorizer.py b/colorhash/colorizer.py
index 970c34b..7355d57 100644
--- a/colorhash/colorizer.py
+++ b/colorhash/colorizer.py
@@ -2,11 +2,12 @@
import abc
from typing import Sequence
+from .color import Color
from .matricizer import Matrix
from .palettes import Palette
-StrMatrix = Sequence[Sequence[str]]
+ColorMatrix = Sequence[Sequence[Color]]
class Colorizer(metaclass=abc.ABCMeta):
@@ -18,7 +19,7 @@ class Colorizer(metaclass=abc.ABCMeta):
"""
@abc.abstractmethod
- def colorize(self, matrix: Matrix) -> StrMatrix:
+ def colorize(self, matrix: Matrix) -> ColorMatrix:
"""
Colorize a matrix.
@@ -41,7 +42,7 @@ class PaletteColorizer(Colorizer):
"""
self.palette = palette
- def colorize(self, matrix: Matrix) -> StrMatrix:
+ def colorize(self, matrix: Matrix) -> ColorMatrix:
"""
Colorize the given matrix using this colorizer's palette.
diff --git a/colorhash/palettes.py b/colorhash/palettes.py
index 7a55403..c14d3b3 100644
--- a/colorhash/palettes.py
+++ b/colorhash/palettes.py
@@ -1,22 +1,24 @@
"Base color palette definitions."
import abc
-from typing import Sequence, Self
+from typing import Sequence
+
+from .color import Color, HSLColor
class Palette(metaclass=abc.ABCMeta):
"""
A 16-color palette.
- All colors must be HTML color strings.
+ All colors must be a `colorhash.Color`.
"""
@abc.abstractmethod
- def choose(self, color: int) -> str:
+ def choose(self, color: int) -> Color:
"""
Chooses the given color in this palette.
"""
- def __getitem__(self, color: int) -> str:
+ def __getitem__(self, color: int) -> Color:
return self.choose(color)
@@ -25,7 +27,7 @@ class StaticPalette(Palette):
A static color palette with discrete colors.
"""
- def __init__(self, colors: Sequence[str]) -> None:
+ def __init__(self, colors: Sequence[Color]) -> None:
"""
Creates a new static color palette.
@@ -35,13 +37,13 @@ class StaticPalette(Palette):
raise ValueError(f"palette must have exactly 16 colors (got {len(colors)})")
self.colors = colors
- def choose(self, color: int) -> str:
+ def choose(self, color: int) -> Color:
if not isinstance(color, int):
raise KeyError("palette color indices must be an integer")
return self.colors[color]
-HSVRange = range | float | int | list[float | int]
+HSLRange = range | float | int | list[float | int]
def quantize(r: range, steps: int = 16) -> list[float]:
@@ -57,7 +59,7 @@ def quantize(r: range, steps: int = 16) -> list[float]:
return [r.start + (i * dist / (steps - 1)) for i in range(steps)]
-def hsl_colors(hue: HSVRange, sat: HSVRange, light: HSVRange) -> list[str]:
+def hsl_colors(hue: HSLRange, sat: HSLRange, light: HSLRange) -> list[HSLColor]:
"""
Utility method to create 16 colors using HSL.
@@ -84,7 +86,7 @@ def hsl_colors(hue: HSVRange, sat: HSVRange, light: HSVRange) -> list[str]:
light = quantize(light)
assert len(light) == 16, "light values must be a list of 16 elements"
- return [f"hsl({h:.02f},{s:.02f}%,{l:.02f}%)" for h, s, l in zip(hue, sat, light)]
+ return [HSLColor(round(h), round(s), round(l)) for h, s, l in zip(hue, sat, light)]
GRADIENT_PALETTES = {
diff --git a/colorhash/svg.py b/colorhash/svg.py
index 88c8cab..1e168ae 100644
--- a/colorhash/svg.py
+++ b/colorhash/svg.py
@@ -1,8 +1,8 @@
"SVG-related functions."
-from .colorizer import StrMatrix
+from .colorizer import ColorMatrix
-def gensvg(matrix: StrMatrix, square_size: int) -> str:
+def gensvg(matrix: ColorMatrix, square_size: int) -> str:
"""
Generate an SVG based on a given matrix.
@@ -22,7 +22,7 @@ def gensvg(matrix: StrMatrix, square_size: int) -> str:
x = c * square_size
y = r * square_size
color = matrix[r][c]
- svg += f' \n'
+ svg += f' \n'
# Close SVG string
svg += ""