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 colors contain authored names and hex values. They replace system colors by normalized name, append otherwise, and are reported through structured load events.
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:
objectImmutable 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 (
str) – Color name (e.g., “coral”, “lightblue”, “darkslategray”)hex (
str) – Hex color code with # prefix (e.g., “#FF7F50”)rgb (
Tuple[int,int,int]) – 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 (
str) – 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)
- classmethod from_rgb(rgb, *, name=None, source='custom', auto_name=False)[source]
Create a record from 8-bit RGB, computing all derived color values.
Explicit names take precedence over auto-naming. Without either, the name is
Color 1. Auto-naming uses the built-in CSS/descriptive namer. Invalid channel counts, non-integers, and out-of-range values raise ValueError rather than being coerced or clamped.Example
>>> ColorRecord.from_rgb((255, 0, 0), auto_name=True).name 'red'
- classmethod from_hex(hex_code, *, name=None, source='custom', auto_name=False)[source]
Create a record from RGB/RGBA/RRGGBB/RRGGBBAA, optionally prefixed with #.
Naming follows from_rgb(); malformed hex raises ValueError. Alpha uses CSS/PNG ordering and is validated then dropped.
Example
>>> ColorRecord.from_hex("#f00", auto_name=True).name 'red'
- Return type:
- Parameters:
- to_swatch_png(*, width=64, height=64, mode='solid')[source]
Render a dependency-free PNG swatch for this color.
A color record has one RGB component, so every supported mode produces the same solid image. Dimensions must be positive built-in integers.
- color_tools.palette.load_system_colors(json_path)[source]
Load one core-format color file without applying user overlays.
- Return type:
- Parameters:
- color_tools.palette.load_colors(json_path=None, *, include_report=False)[source]
Load and merge the color database, optionally returning structured changes.
- Overloads:
- Parameters:
- Return type:
List[ColorRecord] | tuple[List[ColorRecord], ColorLoadReport]
Loads colors from both the core colors.json file and optional user/user-colors.json file in the data directory. User colors replace records by normalized name or append in file order. Effective RGB values must remain unique.
- 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.include_report (
bool) – When true, return the records together with a typed summary of user additions and name replacements.
- Returns:
The merged records, or
(records, report)wheninclude_reportis true.- Return type:
List[ColorRecord] | tuple[List[ColorRecord], ColorLoadReport]
- color_tools.palette.load_palette(name, json_path=None)[source]
Load a named retro palette from the palettes directory.
Built-in palettes are loaded from
data/palettes/{name}.json. User palettes are loaded fromdata/user/palettes/user-{name}.jsonand must be requested with theuser-prefix. Built-in and user palettes use the same versioned document envelope. User palette color records require onlynameandhex; all other color-space values are calculated from the hex value when loaded. Files ending inexample.jsonare excluded from palette discovery.Built-in system palettes are presentation-style sRGB representations for simulating how an image would look on the named platform. They are not guarantees of electrically exact output from every hardware revision, video standard, signal path, display, capture, specification, or emulator.
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: GB Studio 4-shade authoring palette - gameboy-dmg: Original Game Boy screen approximation - gameboy-light: Backlit Game Boy Light screen approximation - gameboy-pocket: Neutral 4-shade Game Boy Pocket grayscale - ti-99-4a: TI-99/4A CRT-style TMS9918 palette - colecovision: ColecoVision CRT-style TMS9918 palette - sg-1000: SG-1000 CRT-style TMS9918 palette - msx1: MSX1 CRT-style TMS9918 palette - bbc-micro: BBC Micro 8-color physical palette - zx-spectrum: ZX Spectrum 15-color display approximation - amstrad-cpc: Amstrad CPC 27-color hardware palette - tic80: TIC-80 default SWEETIE-16 palette
- Parameters:
- Return type:
- Returns:
Palette object loaded from the specified palette file
- Raises:
FileNotFoundError – If the palette file doesn’t exist
ValueError – If the palette file is malformed or contains invalid data
Example
>>> cga = load_palette("cga4") >>> color, dist = cga.nearest_color((128, 64, 200)) >>> print(f"Nearest CGA color: {color.name}")
>>> # Load custom user palette from data/user/palettes/user-mycustom.json >>> custom = load_palette("user-mycustom") >>> print(f"Loaded {len(custom.records)} colors from user palette")
- class color_tools.palette.Palette(records)[source]
Bases:
objectCSS 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 from_rgb(rgb_colors, *, names=None, name_prefix='Color', source='custom', auto_name=False)[source]
Build a searchable palette, preserving input order and duplicates.
Explicit names must match the color count and override auto-naming. Otherwise names are
Color 1,Color 2, etc. (or name_prefix). With auto_name=True, the built-in namer receives the entire RGB list for near-match uniqueness checks. Generated names are not deduplicated. Empty input creates an empty palette. Invalid RGB or name counts raise ValueError.Example
>>> Palette.from_rgb([(255, 0, 0)], auto_name=True).records[0].name 'red'
- classmethod from_hex(hex_colors, *, names=None, name_prefix='Color', source='custom', auto_name=False)[source]
Build a palette from RGB/RGBA/RRGGBB/RRGGBBAA strings, with optional #.
Naming, ordering, and validation follow from_rgb(). Invalid hex raises ValueError. CSS/PNG alpha is validated then dropped.
Example
>>> Palette.from_hex(["#f00", "00ff00"]).records[1].hex '#00FF00'
- 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:
- save_lut_png(path)[source]
Export this palette as an N×1 RGB PNG lookup texture.
Colors retain their palette order and duplicate entries. The registered
palette_lutexporter owns encoding, validation, and directory creation. The returned path is the destination supplied by the caller.- Parameters:
- Return type:
- Returns:
The destination as a
Path.- Raises:
ValueError – If the palette is empty.
OSError – If creating directories or writing the file fails.
- find_by_name(name)[source]
Find color by trimmed, case-insensitive name.
- Return type:
- Parameters:
name (str)
- 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:
Exact distance ties prefer records from
user-*sources. Remaining ties retain the records’ original order. An empty palette returns(None, inf).
- 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:
Exact distance ties prefer records from
user-*sources. Remaining ties retain the records’ original order.