← back to Fentucci Naturals

scripts/color-names.py

68 lines

#!/usr/bin/env python3
"""Derive interior-designer-lexicon color names from dominant hue of each local image.
Simple hue/sat/lightness bucket -> name table (enrichment pass comes later).
Writes data/colors.json: { mfr_sku: {hex, name} }
"""
import json, os, sys, colorsys
from PIL import Image

ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
products = json.load(open(os.path.join(ROOT, 'data', 'products.json')))

def dominant_color(path):
    im = Image.open(path).convert('RGB')
    # center crop 60% to avoid borders/shadows, then shrink
    w, h = im.size
    im = im.crop((int(w*0.2), int(h*0.2), int(w*0.8), int(h*0.8))).resize((50, 50))
    px = list(im.getdata())
    r = sum(p[0] for p in px)//len(px); g = sum(p[1] for p in px)//len(px); b = sum(p[2] for p in px)//len(px)
    return r, g, b

def name_for(r, g, b):
    hh, ll, ss = colorsys.rgb_to_hls(r/255, g/255, b/255)
    hue = hh*360
    # neutrals first
    if ss < 0.09:
        if ll > 0.88: return 'Alabaster'
        if ll > 0.75: return 'Ivory'
        if ll > 0.60: return 'Greige'
        if ll > 0.45: return 'Pewter'
        if ll > 0.30: return 'Slate'
        if ll > 0.15: return 'Charcoal'
        return 'Ebony'
    warm_neutral = ss < 0.25
    if hue < 15 or hue >= 345:
        return 'Blush' if ll > 0.7 else ('Terracotta' if ll > 0.4 else 'Garnet')
    if hue < 45:
        if warm_neutral:
            return 'Oatmeal' if ll > 0.7 else ('Fawn' if ll > 0.45 else 'Espresso')
        return 'Sand' if ll > 0.7 else ('Amber' if ll > 0.45 else 'Sienna')
    if hue < 70:
        return 'Champagne' if ll > 0.72 else ('Ochre' if ll > 0.45 else 'Bronze')
    if hue < 100:
        return 'Flax' if ll > 0.72 else ('Sage' if ll > 0.45 else 'Olive')
    if hue < 165:
        return 'Celadon' if ll > 0.65 else ('Moss' if ll > 0.4 else 'Forest')
    if hue < 200:
        return 'Seaglass' if ll > 0.65 else ('Verdigris' if ll > 0.4 else 'Teal')
    if hue < 250:
        return 'Sky' if ll > 0.65 else ('Denim' if ll > 0.4 else 'Indigo')
    if hue < 290:
        return 'Lilac' if ll > 0.65 else ('Plum' if ll > 0.4 else 'Aubergine')
    return 'Rose' if ll > 0.65 else ('Mulberry' if ll > 0.4 else 'Bordeaux')

out, errs = {}, []
for p in products:
    path = os.path.join(ROOT, p['image_local'])
    if not os.path.exists(path):
        errs.append(p['mfr_sku']); continue
    try:
        r, g, b = dominant_color(path)
        out[p['mfr_sku']] = {'hex': '#%02x%02x%02x' % (r, g, b), 'name': name_for(r, g, b)}
    except Exception as e:
        errs.append(p['mfr_sku'])

json.dump(out, open(os.path.join(ROOT, 'data', 'colors.json'), 'w'), indent=1)
print(f"colored: {len(out)}, missing/error: {len(errs)}")
if errs: print('errors:', errs[:20])