113 lines
4.0 KiB
Python
113 lines
4.0 KiB
Python
#!/usr/bin/env python3
|
||
import sys
|
||
import random
|
||
import colorsys
|
||
import math
|
||
from .color_data import COLORS, color_families
|
||
|
||
# ----------------------------
|
||
# Conversion & Utility Functions
|
||
# ----------------------------
|
||
def hex_to_rgb(hex_code):
|
||
hex_code = hex_code.lstrip('#')
|
||
r = int(hex_code[0:2], 16) / 255.0
|
||
g = int(hex_code[2:4], 16) / 255.0
|
||
b = int(hex_code[4:6], 16) / 255.0
|
||
return (r, g, b)
|
||
|
||
def rgb_to_hex(rgb):
|
||
return '#{:02x}{:02x}{:02x}'.format(
|
||
int(rgb[0] * 255),
|
||
int(rgb[1] * 255),
|
||
int(rgb[2] * 255)
|
||
)
|
||
|
||
def hex_to_rgb_tuple(hex_code):
|
||
hex_code = hex_code.lstrip('#')
|
||
return (int(hex_code[0:2], 16),
|
||
int(hex_code[2:4], 16),
|
||
int(hex_code[4:6], 16))
|
||
|
||
def color_distance(rgb1, rgb2):
|
||
return math.sqrt(sum((a - b) ** 2 for a, b in zip(rgb1, rgb2)))
|
||
|
||
def find_closest_color_name(target_hex, color_dict):
|
||
target_rgb = hex_to_rgb_tuple(target_hex)
|
||
closest_name = None
|
||
min_dist = float('inf')
|
||
for name, hex_code in color_dict.items():
|
||
current_rgb = hex_to_rgb_tuple(hex_code)
|
||
dist = color_distance(target_rgb, current_rgb)
|
||
if dist < min_dist:
|
||
min_dist = dist
|
||
closest_name = name
|
||
return closest_name
|
||
|
||
def shift_hue(hex_code, degree_offset):
|
||
r, g, b = hex_to_rgb(hex_code)
|
||
H, L, S = colorsys.rgb_to_hls(r, g, b)
|
||
offset = degree_offset / 360.0
|
||
new_H = (H + offset) % 1.0
|
||
new_rgb = colorsys.hls_to_rgb(new_H, L, S)
|
||
return rgb_to_hex(new_rgb)
|
||
|
||
# ----------------------------
|
||
# Double Complementary (Tetradic) Palette Functions
|
||
# ----------------------------
|
||
def tetradic_palette(hex_code, shift_fraction=1/6):
|
||
"""
|
||
Generate a tetradic palette from a base hex color.
|
||
Returns 4 hex color codes that form two complementary pairs.
|
||
"""
|
||
base_rgb = hex_to_rgb(hex_code)
|
||
base_h, base_l, base_s = colorsys.rgb_to_hls(*base_rgb)
|
||
h1 = base_h
|
||
h2 = (base_h + 0.5) % 1.0
|
||
h3 = (base_h + shift_fraction) % 1.0
|
||
h4 = (base_h + shift_fraction + 0.5) % 1.0
|
||
return [rgb_to_hex(colorsys.hls_to_rgb(h1, base_l, base_s)),
|
||
rgb_to_hex(colorsys.hls_to_rgb(h2, base_l, base_s)),
|
||
rgb_to_hex(colorsys.hls_to_rgb(h3, base_l, base_s)),
|
||
rgb_to_hex(colorsys.hls_to_rgb(h4, base_l, base_s))]
|
||
|
||
def generate_random_base_color():
|
||
families = ['Reds', 'Pinks', 'Oranges', 'Yellows', 'Greens', 'Blues', 'Purples']
|
||
candidate_colors = []
|
||
for fam in families:
|
||
candidate_colors.extend(color_families.get(fam, []))
|
||
random_color_name = random.choice(candidate_colors)
|
||
return random_color_name, COLORS[random_color_name]
|
||
|
||
def generate_double_complementary_palette_4():
|
||
"""Generate a 4-color double complementary (tetradic) palette."""
|
||
greys = set(color_families.get("Greys", []))
|
||
while True:
|
||
_, base_hex = generate_random_base_color()
|
||
palette_hexes = tetradic_palette(base_hex)
|
||
palette_names = [find_closest_color_name(h, COLORS) for h in palette_hexes]
|
||
if len(set(palette_names)) == 4 and not any(palette_names.count(n) > 1 for n in greys):
|
||
return palette_names
|
||
|
||
def generate_double_complementary_palette_5():
|
||
"""
|
||
Generate a 5-color double complementary (tetradic) palette.
|
||
We start with the 4-color palette and add an extra analogous variant of the base (e.g. base+30°)
|
||
if it’s unique.
|
||
"""
|
||
while True:
|
||
base_name, base_hex = generate_random_base_color()
|
||
base_palette = tetradic_palette(base_hex)
|
||
extra = shift_hue(base_hex, 30)
|
||
palette_hexes = base_palette + [extra]
|
||
palette_names = [find_closest_color_name(h, COLORS) for h in palette_hexes]
|
||
if len(set(palette_names)) == 5:
|
||
return palette_names
|
||
|
||
if __name__ == '__main__':
|
||
five_flag = '-5' in sys.argv
|
||
if five_flag:
|
||
palette = generate_double_complementary_palette_5()
|
||
else:
|
||
palette = generate_double_complementary_palette_4()
|
||
print("Double Complementary Palette (Color Names):", palette)
|