486 lines
17 KiB
Python
486 lines
17 KiB
Python
#!/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.")
|