You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

248 lines
8.9 KiB

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<LightingDirection, string> = {
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<LightingDirection, string> = {
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");
}