#!/usr/bin/env python3 """ Generate basic .cube LUT files for Ralpha Kitchen Sink project These are 17x17x17 or 33x33x33 3D LUTs in Adobe/Resolve compatible format """ import os import math def write_cube_header(f, title, size=33): f.write(f'TITLE "{title}"\n') f.write(f'LUT_3D_SIZE {size}\n') f.write('DOMAIN_MIN 0.0 0.0 0.0\n') f.write('DOMAIN_MAX 1.0 1.0 1.0\n\n') def clamp(v, min_v=0.0, max_v=1.0): return max(min_v, min(max_v, v)) def generate_neutral_lut(output_path, size=33): """Identity LUT - no change""" with open(output_path, 'w') as f: write_cube_header(f, "Neutral", size) for b in range(size): for g in range(size): for r in range(size): rf = r / (size - 1) gf = g / (size - 1) bf = b / (size - 1) f.write(f'{rf:.6f} {gf:.6f} {bf:.6f}\n') def generate_teal_orange_lut(output_path, size=33): """Teal & Orange Hollywood blockbuster look""" with open(output_path, 'w') as f: write_cube_header(f, "Teal Orange", size) for b in range(size): for g in range(size): for r in range(size): rf = r / (size - 1) gf = g / (size - 1) bf = b / (size - 1) # Luminance lum = 0.299 * rf + 0.587 * gf + 0.114 * bf # Push shadows toward teal, highlights toward orange if lum < 0.5: # Shadows: add teal (cyan-ish) factor = (0.5 - lum) * 0.3 rf = clamp(rf - factor * 0.2) gf = clamp(gf + factor * 0.1) bf = clamp(bf + factor * 0.25) else: # Highlights: add orange/warmth factor = (lum - 0.5) * 0.3 rf = clamp(rf + factor * 0.2) gf = clamp(gf + factor * 0.05) bf = clamp(bf - factor * 0.15) # Slight S-curve contrast rf = clamp(0.5 + (rf - 0.5) * 1.15) gf = clamp(0.5 + (gf - 0.5) * 1.15) bf = clamp(0.5 + (bf - 0.5) * 1.15) f.write(f'{rf:.6f} {gf:.6f} {bf:.6f}\n') def generate_vintage_lut(output_path, size=33): """Faded vintage look with lifted blacks""" with open(output_path, 'w') as f: write_cube_header(f, "Vintage Faded", size) for b in range(size): for g in range(size): for r in range(size): rf = r / (size - 1) gf = g / (size - 1) bf = b / (size - 1) # Lift blacks lift = 0.08 rf = lift + rf * (1 - lift) gf = lift + gf * (1 - lift) bf = lift + bf * (1 - lift) # Reduce contrast rf = 0.5 + (rf - 0.5) * 0.85 gf = 0.5 + (gf - 0.5) * 0.85 bf = 0.5 + (bf - 0.5) * 0.85 # Warm tint rf = clamp(rf * 1.05) gf = clamp(gf * 1.0) bf = clamp(bf * 0.9) # Slight desaturation lum = 0.299 * rf + 0.587 * gf + 0.114 * bf sat = 0.7 rf = clamp(lum + (rf - lum) * sat) gf = clamp(lum + (gf - lum) * sat) bf = clamp(lum + (bf - lum) * sat) f.write(f'{rf:.6f} {gf:.6f} {bf:.6f}\n') def generate_bw_lut(output_path, size=33): """Black and white conversion""" with open(output_path, 'w') as f: write_cube_header(f, "Black White", size) for b in range(size): for g in range(size): for r in range(size): rf = r / (size - 1) gf = g / (size - 1) bf = b / (size - 1) # Luminance with slight contrast boost lum = 0.299 * rf + 0.587 * gf + 0.114 * bf lum = clamp(0.5 + (lum - 0.5) * 1.2) f.write(f'{lum:.6f} {lum:.6f} {lum:.6f}\n') def generate_noir_lut(output_path, size=33): """High contrast film noir B&W""" with open(output_path, 'w') as f: write_cube_header(f, "Film Noir", size) for b in range(size): for g in range(size): for r in range(size): rf = r / (size - 1) gf = g / (size - 1) bf = b / (size - 1) lum = 0.299 * rf + 0.587 * gf + 0.114 * bf # Strong S-curve for high contrast lum = clamp(0.5 + (lum - 0.5) * 1.8) # Crush blacks slightly if lum < 0.15: lum = lum * 0.7 f.write(f'{lum:.6f} {lum:.6f} {lum:.6f}\n') def generate_warm_lut(output_path, size=33): """Warm/golden tones""" with open(output_path, 'w') as f: write_cube_header(f, "Warm", size) for b in range(size): for g in range(size): for r in range(size): rf = r / (size - 1) gf = g / (size - 1) bf = b / (size - 1) # Warm shift rf = clamp(rf * 1.1 + 0.02) gf = clamp(gf * 1.02) bf = clamp(bf * 0.85) f.write(f'{rf:.6f} {gf:.6f} {bf:.6f}\n') def generate_cool_lut(output_path, size=33): """Cool/blue tones""" with open(output_path, 'w') as f: write_cube_header(f, "Cool", size) for b in range(size): for g in range(size): for r in range(size): rf = r / (size - 1) gf = g / (size - 1) bf = b / (size - 1) # Cool shift rf = clamp(rf * 0.9) gf = clamp(gf * 0.98) bf = clamp(bf * 1.1 + 0.02) f.write(f'{rf:.6f} {gf:.6f} {bf:.6f}\n') def generate_cinematic_lut(output_path, size=33): """Cinematic look with crushed blacks and teal/orange""" with open(output_path, 'w') as f: write_cube_header(f, "Cinematic", size) for b in range(size): for g in range(size): for r in range(size): rf = r / (size - 1) gf = g / (size - 1) bf = b / (size - 1) # S-curve contrast rf = clamp(0.5 + (rf - 0.5) * 1.3) gf = clamp(0.5 + (gf - 0.5) * 1.3) bf = clamp(0.5 + (bf - 0.5) * 1.3) # Crush blacks if rf < 0.1: rf *= 0.8 if gf < 0.1: gf *= 0.8 if bf < 0.1: bf *= 0.8 # Teal shadows, orange highlights lum = 0.299 * rf + 0.587 * gf + 0.114 * bf if lum < 0.4: bf = clamp(bf + 0.05) gf = clamp(gf + 0.02) elif lum > 0.6: rf = clamp(rf + 0.03) f.write(f'{rf:.6f} {gf:.6f} {bf:.6f}\n') def generate_bleach_bypass_lut(output_path, size=33): """Bleach bypass / desaturated high contrast""" with open(output_path, 'w') as f: write_cube_header(f, "Bleach Bypass", size) for b in range(size): for g in range(size): for r in range(size): rf = r / (size - 1) gf = g / (size - 1) bf = b / (size - 1) # Luminance lum = 0.299 * rf + 0.587 * gf + 0.114 * bf # Desaturate significantly sat = 0.4 rf = clamp(lum + (rf - lum) * sat) gf = clamp(lum + (gf - lum) * sat) bf = clamp(lum + (bf - lum) * sat) # High contrast rf = clamp(0.5 + (rf - 0.5) * 1.5) gf = clamp(0.5 + (gf - 0.5) * 1.5) bf = clamp(0.5 + (bf - 0.5) * 1.5) f.write(f'{rf:.6f} {gf:.6f} {bf:.6f}\n') def generate_sepia_lut(output_path, size=33): """Sepia toned monochrome""" with open(output_path, 'w') as f: write_cube_header(f, "Sepia", size) for b in range(size): for g in range(size): for r in range(size): rf = r / (size - 1) gf = g / (size - 1) bf = b / (size - 1) lum = 0.299 * rf + 0.587 * gf + 0.114 * bf # Sepia toning rf = clamp(lum * 1.2) gf = clamp(lum * 0.95) bf = clamp(lum * 0.7) f.write(f'{rf:.6f} {gf:.6f} {bf:.6f}\n') def generate_vibrant_lut(output_path, size=33): """Enhanced saturation and vibrancy""" with open(output_path, 'w') as f: write_cube_header(f, "Vibrant", size) for b in range(size): for g in range(size): for r in range(size): rf = r / (size - 1) gf = g / (size - 1) bf = b / (size - 1) lum = 0.299 * rf + 0.587 * gf + 0.114 * bf # Boost saturation sat = 1.4 rf = clamp(lum + (rf - lum) * sat) gf = clamp(lum + (gf - lum) * sat) bf = clamp(lum + (bf - lum) * sat) # Slight contrast boost rf = clamp(0.5 + (rf - 0.5) * 1.1) gf = clamp(0.5 + (gf - 0.5) * 1.1) bf = clamp(0.5 + (bf - 0.5) * 1.1) f.write(f'{rf:.6f} {gf:.6f} {bf:.6f}\n') def generate_matte_lut(output_path, size=33): """Low contrast matte look""" with open(output_path, 'w') as f: write_cube_header(f, "Matte", size) for b in range(size): for g in range(size): for r in range(size): rf = r / (size - 1) gf = g / (size - 1) bf = b / (size - 1) # Lift blacks lift = 0.1 rf = lift + rf * (1 - lift * 2) gf = lift + gf * (1 - lift * 2) bf = lift + bf * (1 - lift * 2) # Reduce contrast rf = clamp(0.5 + (rf - 0.5) * 0.75) gf = clamp(0.5 + (gf - 0.5) * 0.75) bf = clamp(0.5 + (bf - 0.5) * 0.75) f.write(f'{rf:.6f} {gf:.6f} {bf:.6f}\n') def generate_cross_process_lut(output_path, size=33): """Cross-processed look with color shifts""" with open(output_path, 'w') as f: write_cube_header(f, "Cross Process", size) for b in range(size): for g in range(size): for r in range(size): rf = r / (size - 1) gf = g / (size - 1) bf = b / (size - 1) # Color channel shifts rf = clamp(rf * 1.1 + 0.05) gf = clamp(gf * 0.95) bf = clamp(bf * 1.2 - 0.05) # High contrast rf = clamp(0.5 + (rf - 0.5) * 1.4) gf = clamp(0.5 + (gf - 0.5) * 1.3) bf = clamp(0.5 + (bf - 0.5) * 1.35) f.write(f'{rf:.6f} {gf:.6f} {bf:.6f}\n') def generate_day_for_night_lut(output_path, size=33): """Day for night effect""" with open(output_path, 'w') as f: write_cube_header(f, "Day For Night", size) for b in range(size): for g in range(size): for r in range(size): rf = r / (size - 1) gf = g / (size - 1) bf = b / (size - 1) # Darken overall rf = rf * 0.4 gf = gf * 0.45 bf = bf * 0.6 # Blue tint rf = clamp(rf * 0.7) gf = clamp(gf * 0.85) bf = clamp(bf * 1.3) f.write(f'{rf:.6f} {gf:.6f} {bf:.6f}\n') def generate_cyberpunk_lut(output_path, size=33): """Neon pink and cyan cyberpunk look""" with open(output_path, 'w') as f: write_cube_header(f, "Cyberpunk", size) for b in range(size): for g in range(size): for r in range(size): rf = r / (size - 1) gf = g / (size - 1) bf = b / (size - 1) lum = 0.299 * rf + 0.587 * gf + 0.114 * bf # High contrast rf = clamp(0.5 + (rf - 0.5) * 1.4) gf = clamp(0.5 + (gf - 0.5) * 1.4) bf = clamp(0.5 + (bf - 0.5) * 1.4) # Push shadows to cyan/teal if lum < 0.4: gf = clamp(gf + 0.1) bf = clamp(bf + 0.15) # Push highlights to pink/magenta elif lum > 0.6: rf = clamp(rf + 0.15) bf = clamp(bf + 0.1) f.write(f'{rf:.6f} {gf:.6f} {bf:.6f}\n') def generate_film_emulation_luts(output_dir, size=33): """Generate film stock emulation LUTs""" # Kodak Portra 400 - warm skin tones, soft pastels with open(os.path.join(output_dir, 'LUT_FilmEmulation_01_Portra400.cube'), 'w') as f: write_cube_header(f, "Kodak Portra 400", size) for b in range(size): for g in range(size): for r in range(size): rf = r / (size - 1) gf = g / (size - 1) bf = b / (size - 1) # Warm up, slight desaturation lum = 0.299 * rf + 0.587 * gf + 0.114 * bf sat = 0.9 rf = clamp(lum + (rf - lum) * sat + 0.02) gf = clamp(lum + (gf - lum) * sat) bf = clamp(lum + (bf - lum) * sat - 0.03) # Soft contrast rf = clamp(0.5 + (rf - 0.5) * 0.95) gf = clamp(0.5 + (gf - 0.5) * 0.95) bf = clamp(0.5 + (bf - 0.5) * 0.95) f.write(f'{rf:.6f} {gf:.6f} {bf:.6f}\n') # Fuji Velvia 50 - ultra saturated, high contrast with open(os.path.join(output_dir, 'LUT_FilmEmulation_07_Velvia50.cube'), 'w') as f: write_cube_header(f, "Fuji Velvia 50", size) for b in range(size): for g in range(size): for r in range(size): rf = r / (size - 1) gf = g / (size - 1) bf = b / (size - 1) lum = 0.299 * rf + 0.587 * gf + 0.114 * bf # High saturation sat = 1.5 rf = clamp(lum + (rf - lum) * sat) gf = clamp(lum + (gf - lum) * sat) bf = clamp(lum + (bf - lum) * sat) # High contrast rf = clamp(0.5 + (rf - 0.5) * 1.3) gf = clamp(0.5 + (gf - 0.5) * 1.3) bf = clamp(0.5 + (bf - 0.5) * 1.3) f.write(f'{rf:.6f} {gf:.6f} {bf:.6f}\n') if __name__ == '__main__': output_dir = os.path.dirname(os.path.abspath(__file__)) print("Generating LUT files...") generate_neutral_lut(os.path.join(output_dir, 'LUT_Neutral.cube')) print(" Created: LUT_Neutral.cube") generate_teal_orange_lut(os.path.join(output_dir, 'LUT_Teal_Orange.cube')) print(" Created: LUT_Teal_Orange.cube") generate_vintage_lut(os.path.join(output_dir, 'LUT_Vintage_01.cube')) print(" Created: LUT_Vintage_01.cube") generate_bw_lut(os.path.join(output_dir, 'LUT_BW_01.cube')) print(" Created: LUT_BW_01.cube") generate_noir_lut(os.path.join(output_dir, 'LUT_BW_04_Noir.cube')) print(" Created: LUT_BW_04_Noir.cube") generate_warm_lut(os.path.join(output_dir, 'LUT_Warm.cube')) print(" Created: LUT_Warm.cube") generate_cool_lut(os.path.join(output_dir, 'LUT_Cool.cube')) print(" Created: LUT_Cool.cube") generate_cinematic_lut(os.path.join(output_dir, 'LUT_Cinematic.cube')) print(" Created: LUT_Cinematic.cube") generate_bleach_bypass_lut(os.path.join(output_dir, 'LUT_Bleach_Bypass.cube')) print(" Created: LUT_Bleach_Bypass.cube") generate_sepia_lut(os.path.join(output_dir, 'LUT_BW_05_Sepia.cube')) print(" Created: LUT_BW_05_Sepia.cube") generate_vibrant_lut(os.path.join(output_dir, 'LUT_Vibrant.cube')) print(" Created: LUT_Vibrant.cube") generate_matte_lut(os.path.join(output_dir, 'LUT_Matte.cube')) print(" Created: LUT_Matte.cube") generate_cross_process_lut(os.path.join(output_dir, 'LUT_Cross_Process.cube')) print(" Created: LUT_Cross_Process.cube") generate_day_for_night_lut(os.path.join(output_dir, 'LUT_Day_For_Night.cube')) print(" Created: LUT_Day_For_Night.cube") generate_cyberpunk_lut(os.path.join(output_dir, 'LUT_Cyberpunk.cube')) print(" Created: LUT_Cyberpunk.cube") generate_film_emulation_luts(output_dir) print(" Created: Film emulation LUTs") print("\nDone! Generated LUT files.")