color_tools.palette

Color palette management with fast lookup.

This module provides: 1. Data class for colors (ColorRecord) 2. Functions to load color data from JSON with user override support 3. Palette class with multiple indices for O(1) lookups 4. Nearest-color search using various distance metrics

For filament management, see color_tools.filament_palette module.

The Palette class is like a database with multiple indexes - you can search by name, RGB, HSL, etc. and get instant results!

User files (user/user-colors.json) always override core data when there are conflicts. Override information is logged for transparency.

Example

>>> from color_tools import Palette
>>>
>>> # Load CSS color database
>>> palette = Palette.load_default()
>>> print(f"Loaded {len(palette.colors)} CSS colors")
Loaded 148 CSS colors
>>>
>>> # Find exact color by name
>>> coral = palette.get_by_name("coral")
>>> print(f"Coral: RGB{coral.rgb} (#{coral.hex})")
Coral: RGB(255, 127, 80) (#FF7F50)
>>>
>>> # Find nearest color to custom RGB
>>> custom_orange = (255, 140, 60)
>>> nearest, distance = palette.nearest_color(custom_orange)
>>> print(f"Nearest to RGB{custom_orange}: {nearest.name} (ΔE: {distance:.1f})")
Nearest to RGB(255, 140, 60): darkorange (ΔE: 7.2)
class color_tools.palette.ColorRecord(name, hex, rgb, hsl, lab, lch, source='colors.json')[source]

Bases: object

Immutable record representing a named CSS color with precomputed color space values.

This dataclass is frozen (immutable) - once created, you can’t change it. This is perfect for colors: a color IS what it IS! 🎨

All color space values are precomputed and stored for fast access without conversion overhead. The source field tracks which JSON file provided this color, enabling user override detection and debugging.

Variables:
  • name – Color name (e.g., “coral”, “lightblue”, “darkslategray”)

  • hex – Hex color code with # prefix (e.g., “#FF7F50”)

  • rgb – RGB color tuple with values 0-255 (e.g., (255, 127, 80))

  • hsl – HSL color tuple (H: 0-360°, S: 0-100%, L: 0-100%)

  • lab – CIE LAB color tuple (L: 0-100, a: ~-128 to +127, b: ~-128 to +127)

  • lch – CIE LCH color tuple (L: 0-100, C: 0-100+, H: 0-360°)

  • source – JSON filename where this record originated (default: “colors.json”)

Parameters:

Example

>>> from color_tools import Palette
>>> palette = Palette.load_default()
>>> color = palette.get_by_name("coral")
>>> print(f"{color.name}: RGB{color.rgb}")
coral: RGB(255, 127, 80)
>>> print(f"LAB: {color.lab}")
LAB: (67.29, 44.61, 49.72)
name: str
hex: str
rgb: Tuple[int, int, int]
hsl: Tuple[float, float, float]
lab: Tuple[float, float, float]
lch: Tuple[float, float, float]
source: str = 'colors.json'
__str__()[source]

Human-readable color representation: name (#hex)

Return type:

str

color_tools.palette.load_colors(json_path=None)[source]

Load CSS color database from JSON files (core + user additions).

Loads colors from both the core colors.json file and optional user/user-colors.json file in the data directory. Core colors are loaded first, followed by user colors.

Parameters:

json_path (Path | str | None) – Path to directory containing JSON files, or path to specific colors JSON file. If None, looks for colors.json in the package’s data/ directory.

Return type:

List[ColorRecord]

Returns:

List of ColorRecord objects (core colors + user colors)

color_tools.palette.load_palette(name, json_path=None)[source]

Load a named retro palette from the palettes directory.

Palettes are loaded from: 1. User palettes: data/user/palettes/{name}.json (if exists) 2. Core palettes: data/palettes/{name}.json (built-in)

User palettes override core palettes with the same name.

Common built-in palettes include: - cga4: CGA 4-color palette (Palette 1, high intensity) - classic gaming! - cga16: CGA 16-color palette (full RGBI) - ega16: EGA 16-color palette (standard/default) - ega64: EGA 64-color palette (full 6-bit RGB) - vga: VGA 256-color palette (Mode 13h) - web: Web-safe 216-color palette (6x6x6 RGB cube) - gameboy: Game Boy 4-shade green palette

Parameters:
  • name (str) – Palette name (e.g., ‘cga4’, ‘ega16’, ‘vga’, ‘web’, or custom user palette)

  • json_path (Path | str | None) – Optional custom data directory. If None, uses package default.

Return type:

Palette

Returns:

Palette object loaded from the specified palette file

Raises:

Example

>>> cga = load_palette("cga4")
>>> color, dist = cga.nearest_color((128, 64, 200))
>>> print(f"Nearest CGA color: {color.name}")
>>> # Load custom user palette
>>> custom = load_palette("my_custom_palette")
>>> print(f"Loaded {len(custom.records)} colors from user palette")
class color_tools.palette.Palette(records)[source]

Bases: object

CSS color palette with multiple indexing strategies for fast lookup.

Think of this as a database with multiple indexes. Want to find a color by name? O(1). By RGB? O(1). By LAB? O(1). The tradeoff is memory - we keep multiple dictionaries pointing to the same ColorRecords.

For a palette with ~150 CSS colors, this is totally fine! 🚀

Parameters:

records (List[ColorRecord])

classmethod load_default()[source]

Load the default CSS color palette from the package data.

This is a convenience method so you don’t have to worry about file paths - just call Palette.load_default() and go!

Return type:

Palette

find_by_name(name)[source]

Find color by exact name match (case-insensitive).

Return type:

Optional[ColorRecord]

Parameters:

name (str)

find_by_rgb(rgb)[source]

Find color by exact RGB match.

Return type:

Optional[ColorRecord]

Parameters:

rgb (Tuple[int, int, int])

find_by_hsl(hsl, rounding=2)[source]

Find color by HSL match (with rounding for fuzzy matching).

Return type:

Optional[ColorRecord]

Parameters:
find_by_lab(lab, rounding=2)[source]

Find color by LAB match (with rounding for fuzzy matching).

Return type:

Optional[ColorRecord]

Parameters:
find_by_lch(lch, rounding=2)[source]

Find color by LCH match (with rounding for fuzzy matching).

Return type:

Optional[ColorRecord]

Parameters:
nearest_color(value, space='lab', metric='de2000', *, cmc_l=2.0, cmc_c=1.0)[source]

Find nearest color by space/metric.

This is the main search function! It iterates through all colors and finds the one with minimum distance in the specified space.

Parameters:
  • value (Tuple[float, float, float]) – Color in the specified space (RGB, HSL, LAB, or LCH)

  • space (str) – Color space - ‘rgb’, ‘hsl’, ‘lab’, or ‘lch’ (default: ‘lab’)

  • metric (str) – Distance metric - ‘euclidean’, ‘de76’, ‘de94’, ‘de2000’, ‘cmc’

  • cmc_l (float) – Parameters for CMC metric (default 2:1 for acceptability)

  • cmc_c (float) – Parameters for CMC metric (default 2:1 for acceptability)

Return type:

Tuple[ColorRecord, float]

Returns:

(nearest_color_record, distance) tuple

nearest_colors(value, space='lab', metric='de2000', count=5, *, cmc_l=2.0, cmc_c=1.0)[source]

Find the nearest N colors by space/metric.

Similar to nearest_color but returns multiple results sorted by distance.

Parameters:
  • value (Tuple[float, float, float]) – Color in the specified space (RGB, HSL, LAB, or LCH)

  • space (str) – Color space - ‘rgb’, ‘hsl’, ‘lab’, or ‘lch’ (default: ‘lab’)

  • metric (str) – Distance metric - ‘euclidean’, ‘de76’, ‘de94’, ‘de2000’, ‘cmc’

  • count (int) – Number of results to return (default: 5, max: 50)

  • cmc_l (float) – Parameters for CMC metric (default 2:1 for acceptability)

  • cmc_c (float) – Parameters for CMC metric (default 2:1 for acceptability)

Return type:

List[Tuple[ColorRecord, float]]

Returns:

List of (color_record, distance) tuples sorted by distance (closest first)

get_override_info()[source]

Get information about user overrides in this palette.

Returns:

{
    "colors": {
        "name": {"color_name": ("core_source", "user_source")},
        "rgb": {"(r,g,b)": ("core_source", "user_source")}
    }
}

Return type:

Dictionary with override information structure