import type { LightingBeamAngle, LightingDirection, LightingPosition, LightingPreset, LightingSettings } from "@/types"; export const DEFAULT_LIGHTING_SETTINGS: LightingSettings = { enabled: false, viewMode: "perspective", smartMode: false, brightness: 50, color: "#ffffff", mainLightDirection: "front", lightPosition: { x: 0, y: 0, z: 1 }, beamAngle: 45, rimLight: false, smartPrompt: "", referenceImage: null, selectedPresetId: null, }; export const LIGHTING_PRESETS: LightingPreset[] = [ { id: "overexposed-film", label: "过曝胶片", thumbnail: "linear-gradient(135deg, #f7ead1 0%, #ffffff 55%, #d8b47f 100%)", prompt: "overexposed film lighting, soft bloom, warm highlights", settings: { brightness: 100, color: "#ffffff", mainLightDirection: "front", lightPosition: { x: 0, y: 0, z: 1 }, beamAngle: 45, smartPrompt: "柯达胶片感的曝光和柔和高光,只调整打光", }, }, { id: "blue-backlight", label: "蓝色逆光", thumbnail: "linear-gradient(135deg, #080b18 0%, #1646ff 60%, #dbe8ff 100%)", prompt: "blue backlight, cool rim glow, dramatic contrast", settings: { brightness: 50, color: "#3438ff", mainLightDirection: "bottom", lightPosition: { x: 0, y: 1, z: 0 }, beamAngle: 45, rimLight: false, smartPrompt: "", }, }, { id: "rembrandt", label: "伦勃朗光", thumbnail: "linear-gradient(135deg, #1b120d 0%, #b9824c 55%, #f3d3a0 100%)", prompt: "Rembrandt lighting, painterly side key light, soft shadow triangle", settings: { brightness: 50, color: "#ffffff", mainLightDirection: "left", lightPosition: { x: -0.58, y: -0.58, z: 0.58 }, beamAngle: 45, smartPrompt: "", }, }, { id: "cyberpunk", label: "赛博朋克", thumbnail: "linear-gradient(135deg, #06121c 0%, #00d1ff 45%, #ff2bd6 100%)", prompt: "cyberpunk neon lighting, teal and magenta glow, high contrast", settings: { brightness: 50, color: "#ffffff", mainLightDirection: "front", lightPosition: { x: 0, y: 0, z: 1 }, beamAngle: 45, rimLight: false, smartPrompt: "《银翼杀手2045》同款青紫高反差打光,只调整光影", }, }, { id: "sunset-dream", label: "落日迷幻", thumbnail: "linear-gradient(135deg, #2a0f2d 0%, #ff7b3a 58%, #ffd36a 100%)", prompt: "dreamy sunset lighting, warm low-angle glow, hazy atmosphere", settings: { brightness: 50, color: "#ffffff", mainLightDirection: "front", lightPosition: { x: 0, y: 0, z: 1 }, beamAngle: 45, smartPrompt: "", }, }, { id: "mystic-low-key", label: "神秘暗调", thumbnail: "linear-gradient(135deg, #030303 0%, #18201d 55%, #5d806f 100%)", prompt: "mysterious low-key lighting, deep shadows, subtle green highlights", settings: { brightness: 10, color: "#ffffff", mainLightDirection: "front", lightPosition: { x: 0, y: 0, z: 1 }, beamAngle: 45, smartPrompt: "百叶窗打光,神秘优雅", }, }, { id: "golden-hour", label: "黄金时刻", thumbnail: "linear-gradient(135deg, #3b1e09 0%, #ffb33c 50%, #fff1b9 100%)", prompt: "golden hour lighting, soft warm sunlight, natural glow", settings: { brightness: 50, color: "#ffffff", mainLightDirection: "front", lightPosition: { x: 0, y: 0, z: 1 }, beamAngle: 45, smartPrompt: "黄金时刻暖色光影,只调整光照和阴影", }, }, { id: "cool-morning", label: "清晨冷灰", thumbnail: "linear-gradient(135deg, #20242b 0%, #8796a8 55%, #dbe5ee 100%)", prompt: "cool morning light, desaturated grey-blue ambience, gentle shadows", settings: { brightness: 50, color: "#ffffff", mainLightDirection: "front", lightPosition: { x: 0, y: 0, z: 1 }, beamAngle: 45, smartPrompt: "清晨冷灰色温和柔和阴影,只调整光照和色温", }, }, ]; const DIRECTION_LABELS: Record = { left: "left side key light", top: "top key light", right: "right side key light", front: "front key light", bottom: "bottom uplight", back: "back light", }; const DIRECTION_INSTRUCTIONS: Record = { left: "Place the main light on the left side of the subject and cast natural shadows to the right.", top: "Place the main light above the subject and create soft downward shadows.", right: "Place the main light on the right side of the subject and cast natural shadows to the left.", front: "Place the main light in front of the subject for clear facial illumination.", bottom: "Place the main light below the subject for controlled uplighting.", back: "Place the main light behind the subject and create a clean backlit silhouette.", }; function brightnessInstruction(value: number): string { if (value >= 75) return "Use bright high-key illumination without washing out important details."; if (value >= 55) return "Use balanced medium-bright illumination with readable details."; if (value >= 35) return "Use restrained low-key illumination with visible form and controlled shadows."; return "Use dark cinematic illumination while keeping the subject recognizable."; } function positionInstruction(position: LightingPosition): string { const horizontal = position.x <= -0.25 ? "camera-left" : position.x >= 0.25 ? "camera-right" : "centered"; const vertical = position.y <= -0.25 ? "above the subject" : position.y >= 0.25 ? "below the subject" : "near eye level"; const depth = position.z <= -0.25 ? "behind the subject" : position.z >= 0.25 ? "in front of the subject" : "on the subject plane"; return [ `light source vector x=${position.x.toFixed(2)}`, `y=${position.y.toFixed(2)}`, `z=${position.z.toFixed(2)}`, `${horizontal}, ${vertical}, ${depth}`, "aim the light directly at the center of the source image", ].join(", "); } function beamAngleInstruction(angle: LightingBeamAngle): string { const width = Math.abs(angle) === 45 ? "focused flashlight-like beam" : "wide studio softbox spread"; const tilt = angle < 0 ? "negative tilt angle" : "positive tilt angle"; return `${angle} degree beam angle, ${width}, ${tilt}`; } export function normalizeLightingSettings(settings?: LightingSettings | null): LightingSettings { const current = settings ?? null; return { ...DEFAULT_LIGHTING_SETTINGS, ...(current ?? {}), lightPosition: { ...DEFAULT_LIGHTING_SETTINGS.lightPosition, ...(current?.lightPosition ?? {}), }, }; } export function buildLightingPrompt(settings?: LightingSettings | null): string { const lighting = normalizeLightingSettings(settings); if (!lighting.enabled) return ""; const preset = LIGHTING_PRESETS.find((item) => item.id === lighting.selectedPresetId); const parts = [ "Lighting plan:", DIRECTION_LABELS[lighting.mainLightDirection], DIRECTION_INSTRUCTIONS[lighting.mainLightDirection], `${lighting.brightness}% brightness`, brightnessInstruction(lighting.brightness), `light color ${lighting.color}`, positionInstruction(lighting.lightPosition), beamAngleInstruction(lighting.beamAngle), ]; if (lighting.rimLight) { parts.push("visible rim light around the subject"); } if (preset?.prompt) { parts.push(preset.prompt); } if (lighting.smartMode && lighting.smartPrompt.trim()) { parts.push(lighting.smartPrompt.trim()); } return parts.join(", "); } export function buildOptimizedLightingPrompt( basePrompt: string | null | undefined, settings?: LightingSettings | null ): string { const lightingPrompt = buildLightingPrompt(settings); const userPrompt = (basePrompt ?? "").trim(); if (!lightingPrompt) { return userPrompt; } const userIntent = userPrompt ? `User intent: ${userPrompt}` : "User intent: relight the uploaded image according to the lighting plan."; return [ "STRICT image-to-image relighting task: use the uploaded image as the locked source image.", userIntent, lightingPrompt, "The output must show the same exact person from the source image: identical face identity, facial features, age, expression, hair, body shape, hands, pose, clothing, accessories, and camera/phone.", "Preserve the original composition, camera angle, crop, background, object layout, readable text overlays, and image style.", "Only change illumination: light direction, brightness, color temperature, shadows, highlights, reflections, and ambient lighting mood required by the lighting plan.", "Treat any preset or style wording as a lighting reference only, not permission to redesign the person, outfit, background, pose, scene, or photographic composition.", "Do not add or remove people or objects, do not beautify or re-age the person, do not replace the face, do not change hairstyle or clothing, do not crop the subject, and do not create a different scene.", ].join("\n"); }