import { CinemaSeason } from '../types'; // Cinematic seasonal atmosphere grade. Deliberately NOT a fake re-light: a // still photo has no depth, so "moving the sun" would look plastic. Instead // this reads the photo's EXISTING exposure and pushes it a certain way: // - lit areas get the season's warm/golden tint + a soft sun glow (halation) // - shadow side gets a complementary cool tint // - a seasonal haze/fog veil blends toward a fog color (strongest in winter) // That reads as "sunlight / morning mist" without inventing new light. // // Uniforms are 16 flat floats (scalar form keeps the JSX uniforms // object and the worklet/export flat arrays in the exact same order): // strength, contrast, saturation, haze, // warmR, warmG, warmB, warmAmt, // coolR, coolG, coolB, coolAmt, // fogR, fogG, fogB, fogAmt export const CINEMA_SKSL = ` uniform shader src; uniform float strength; uniform float contrast; uniform float saturation; uniform float haze; uniform float warmR; uniform float warmG; uniform float warmB; uniform float warmAmt; uniform float coolR; uniform float coolG; uniform float coolB; uniform float coolAmt; uniform float fogR; uniform float fogG; uniform float fogB; uniform float fogAmt; vec4 main(vec2 xy) { vec4 c = src.eval(xy); float t = clamp(dot(c.rgb, vec3(0.2126, 0.7152, 0.0722)), 0.0, 1.0); vec3 r = c.rgb; // Filmic-ish gentle contrast on the luma (keeps hue). r = mix(r, clamp((r - 0.5) * (1.0 + contrast * strength) + 0.5, 0.0, 1.0), strength); // Saturation. float l = dot(r, vec3(0.2126, 0.7152, 0.0722)); r = mix(vec3(l), r, mix(1.0, saturation, strength)); // Season tints follow the EXISTING light: warm on lit areas, cool in shade. float lit = smoothstep(0.30, 1.0, t); float shad = 1.0 - smoothstep(0.0, 0.45, t); vec3 warm = vec3(warmR, warmG, warmB); vec3 cool = vec3(coolR, coolG, coolB); r *= 1.0 + (warm - 1.0) * lit * warmAmt * strength; r *= 1.0 + (cool - 1.0) * shad * coolAmt * strength; // Soft sun glow at the brightest points (halation of existing highlights). float glow = smoothstep(0.65, 1.0, t); r += warm * glow * warmAmt * 0.45 * strength; // Atmospheric haze/fog veil (lightest shadow side => misty air). vec3 fog = vec3(fogR, fogG, fogB); r = mix(r, fog, fogAmt * strength * (0.3 + 0.7 * shad)); return vec4(clamp(r, 0.0, 1.0), c.a); } `; export interface CinemaUniformSet { season: CinemaSeason; // 16 flat floats — order matches CINEMA_SKSL declaration order above. flat: number[]; // Name-keyed, for the JSX form. uniforms: Record; } const ZERO: Record = {}; const SEASONS: Record = { // Soft fresh light, gentle warm haze, barely-there cool in shade. spring: { name: 'CINEMA SPRING', values: [ 0.6, 0.08, 1.04, 0.18, 1.02, 0.998, 0.95, 0.3, 0.97, 0.995, 1.03, 0.12, 0.95, 0.96, 0.98, 0.16, ], }, // Golden hour-ish high sun: strong warm on lit, bluish shade, crisp. summer: { name: 'CINEMA SUMMER', values: [ 0.75, 0.2, 1.12, 0.1, 1.08, 1.04, 0.88, 0.4, 0.93, 0.97, 1.06, 0.2, 1.0, 0.97, 0.88, 0.05, ], }, // Amber low sun + warm atmospheric haze. autumn: { name: 'CINEMA AUTUMN', values: [ 0.8, 0.16, 1.1, 0.24, 1.12, 1.02, 0.78, 0.42, 1.02, 0.98, 0.88, 0.16, 1.0, 0.93, 0.75, 0.18, ], }, // Cold winter fog: desaturated, blue-grey, heavy mist, no warm sun. winter: { name: 'CINEMA WINTER', values: [ 0.65, -0.02, 0.88, 0.38, 0.99, 0.995, 1.02, 0.06, 0.94, 0.97, 1.08, 0.3, 0.9, 0.93, 0.97, 0.45, ], }, }; const KEYS = [ 'strength', 'contrast', 'saturation', 'haze', 'warmR', 'warmG', 'warmB', 'warmAmt', 'coolR', 'coolG', 'coolB', 'coolAmt', 'fogR', 'fogG', 'fogB', 'fogAmt', ] as const; export function getCinemaUniforms(season: CinemaSeason | null | undefined): CinemaUniformSet | null { if (!season) return null; const s = SEASONS[season]; if (!s) return null; const uniforms: Record = {}; KEYS.forEach((k, i) => (uniforms[k] = s.values[i])); return { season, flat: s.values, uniforms }; } // Zeroed 16-float block — used to turn the cinema pass OFF in the worklet // synchronizable (strength === 0 means inactive there). export function cinemaOffFlat(): number[] { return [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; } export function cinemaIsActive(flat: number[] | null | undefined): boolean { return !!flat && flat.length === 16 && flat[0] > 0; }