← back to Nova Recolor
batch_recolor_v2.py
66 lines
#!/usr/bin/env python3
"""Reinhard color-transfer batch: ruby drapery → every Novasuede color.
Source drapery: /Users/macstudio3/Projects/nova-recolor/novasuede-ruby.jpg
Color samples: /tmp/novasuede_sources/*.jpg (downloaded from Shopify CDN)
Output: /tmp/novasuede_drapery/{slug}.jpg
Reinhard 2001 color transfer: matches mean+std of source LAB to target LAB.
"""
import os, glob, json, sys, time
import numpy as np
import cv2
DRAPERY_SRC = '/Users/macstudio3/Projects/nova-recolor/novasuede-ruby.jpg'
SAMPLES_DIR = '/tmp/novasuede_sources'
OUT_DIR = '/tmp/novasuede_drapery'
os.makedirs(OUT_DIR, exist_ok=True)
def center_crop(img, frac=0.7):
h, w = img.shape[:2]
ch, cw = int(h*frac), int(w*frac)
y0, x0 = (h-ch)//2, (w-cw)//2
return img[y0:y0+ch, x0:x0+cw]
def lab_reinhard(src_bgr, tgt_bgr):
"""Reinhard 2001 color transfer in LAB space."""
src_lab = cv2.cvtColor(src_bgr, cv2.COLOR_BGR2LAB).astype(np.float32)
tgt_lab = cv2.cvtColor(tgt_bgr, cv2.COLOR_BGR2LAB).astype(np.float32)
s = src_lab.reshape(-1, 3)
t = tgt_lab.reshape(-1, 3)
s_mean, s_std = s.mean(0), s.std(0)
t_mean, t_std = t.mean(0), t.std(0)
out = (src_lab - s_mean) * (t_std / (s_std + 1e-8)) + t_mean
out = np.clip(out, 0, 255).astype(np.uint8)
return cv2.cvtColor(out, cv2.COLOR_LAB2BGR)
src = cv2.imread(DRAPERY_SRC, cv2.IMREAD_COLOR)
assert src is not None, f"could not read {DRAPERY_SRC}"
samples = sorted(glob.glob(os.path.join(SAMPLES_DIR, '*.jpg')))
print(f"found {len(samples)} color samples")
manifest = []
t0 = time.time()
for s_path in samples:
slug = os.path.splitext(os.path.basename(s_path))[0]
out_path = os.path.join(OUT_DIR, f'{slug}.jpg')
tgt = cv2.imread(s_path, cv2.IMREAD_COLOR)
if tgt is None:
print(f"SKIP unreadable: {s_path}")
continue
tgt_c = center_crop(tgt, 0.7)
out = lab_reinhard(src, tgt_c)
cv2.imwrite(out_path, out, [cv2.IMWRITE_JPEG_QUALITY, 88])
# Also extract a single hex color from the swatch center for the chip
cx, cy = tgt_c.shape[1]//2, tgt_c.shape[0]//2
bgr = tgt_c[cy, cx]
hex_color = '#{:02x}{:02x}{:02x}'.format(bgr[2], bgr[1], bgr[0])
manifest.append({'slug': slug, 'hex': hex_color, 'drapery': f'{slug}.jpg'})
print(f"OK {slug:30s} hex={hex_color}")
with open(os.path.join(OUT_DIR, 'manifest.json'), 'w') as f:
json.dump(manifest, f, indent=2)
print(f"\ndone in {time.time()-t0:.1f}s — {len(manifest)} draperies + manifest.json")