Update colors to be expressed explicitly as RGB or HSL
Generally, palettes are expressed using HSL, but sometimes we want the output to be in RGB (or some other format). Colors are now expressed using either RGBColor or HSLColor and can easily be converted between the two. Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
This commit is contained in:
72
colorhash/color.py
Normal file
72
colorhash/color.py
Normal file
@@ -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
|
||||||
@@ -2,11 +2,12 @@
|
|||||||
import abc
|
import abc
|
||||||
from typing import Sequence
|
from typing import Sequence
|
||||||
|
|
||||||
|
from .color import Color
|
||||||
from .matricizer import Matrix
|
from .matricizer import Matrix
|
||||||
from .palettes import Palette
|
from .palettes import Palette
|
||||||
|
|
||||||
|
|
||||||
StrMatrix = Sequence[Sequence[str]]
|
ColorMatrix = Sequence[Sequence[Color]]
|
||||||
|
|
||||||
|
|
||||||
class Colorizer(metaclass=abc.ABCMeta):
|
class Colorizer(metaclass=abc.ABCMeta):
|
||||||
@@ -18,7 +19,7 @@ class Colorizer(metaclass=abc.ABCMeta):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def colorize(self, matrix: Matrix) -> StrMatrix:
|
def colorize(self, matrix: Matrix) -> ColorMatrix:
|
||||||
"""
|
"""
|
||||||
Colorize a matrix.
|
Colorize a matrix.
|
||||||
|
|
||||||
@@ -41,7 +42,7 @@ class PaletteColorizer(Colorizer):
|
|||||||
"""
|
"""
|
||||||
self.palette = palette
|
self.palette = palette
|
||||||
|
|
||||||
def colorize(self, matrix: Matrix) -> StrMatrix:
|
def colorize(self, matrix: Matrix) -> ColorMatrix:
|
||||||
"""
|
"""
|
||||||
Colorize the given matrix using this colorizer's palette.
|
Colorize the given matrix using this colorizer's palette.
|
||||||
|
|
||||||
|
|||||||
@@ -1,22 +1,24 @@
|
|||||||
"Base color palette definitions."
|
"Base color palette definitions."
|
||||||
import abc
|
import abc
|
||||||
from typing import Sequence, Self
|
from typing import Sequence
|
||||||
|
|
||||||
|
from .color import Color, HSLColor
|
||||||
|
|
||||||
|
|
||||||
class Palette(metaclass=abc.ABCMeta):
|
class Palette(metaclass=abc.ABCMeta):
|
||||||
"""
|
"""
|
||||||
A 16-color palette.
|
A 16-color palette.
|
||||||
|
|
||||||
All colors must be HTML color strings.
|
All colors must be a `colorhash.Color`.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def choose(self, color: int) -> str:
|
def choose(self, color: int) -> Color:
|
||||||
"""
|
"""
|
||||||
Chooses the given color in this palette.
|
Chooses the given color in this palette.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __getitem__(self, color: int) -> str:
|
def __getitem__(self, color: int) -> Color:
|
||||||
return self.choose(color)
|
return self.choose(color)
|
||||||
|
|
||||||
|
|
||||||
@@ -25,7 +27,7 @@ class StaticPalette(Palette):
|
|||||||
A static color palette with discrete colors.
|
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.
|
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)})")
|
raise ValueError(f"palette must have exactly 16 colors (got {len(colors)})")
|
||||||
self.colors = colors
|
self.colors = colors
|
||||||
|
|
||||||
def choose(self, color: int) -> str:
|
def choose(self, color: int) -> Color:
|
||||||
if not isinstance(color, int):
|
if not isinstance(color, int):
|
||||||
raise KeyError("palette color indices must be an integer")
|
raise KeyError("palette color indices must be an integer")
|
||||||
return self.colors[color]
|
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]:
|
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)]
|
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.
|
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)
|
light = quantize(light)
|
||||||
assert len(light) == 16, "light values must be a list of 16 elements"
|
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 = {
|
GRADIENT_PALETTES = {
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
"SVG-related functions."
|
"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.
|
Generate an SVG based on a given matrix.
|
||||||
|
|
||||||
@@ -22,7 +22,7 @@ def gensvg(matrix: StrMatrix, square_size: int) -> str:
|
|||||||
x = c * square_size
|
x = c * square_size
|
||||||
y = r * square_size
|
y = r * square_size
|
||||||
color = matrix[r][c]
|
color = matrix[r][c]
|
||||||
svg += f' <rect x="{x}" y="{y}" width="{square_size}" height="{square_size}" fill="{color}" />\n'
|
svg += f' <rect x="{x}" y="{y}" width="{square_size}" height="{square_size}" fill="{color.to_html_color()}" />\n'
|
||||||
|
|
||||||
# Close SVG string
|
# Close SVG string
|
||||||
svg += "</svg>"
|
svg += "</svg>"
|
||||||
|
|||||||
Reference in New Issue
Block a user