From 12256e16eaa7b99bd890a399671ee94b93a5d24e Mon Sep 17 00:00:00 2001 From: xuzhijie Date: Tue, 12 May 2026 10:31:41 +0800 Subject: [PATCH] Fix reference image handling for lighting - Reference image is always sent as second image to model (not limited to smart free mode), original image stays as first image - Prompt explicitly tells model: first image is source, second is lighting reference to analyze and apply - Preview always shows original image, reference image no longer replaces it in the globe preview - Show uploaded reference image thumbnail with clear button in UI - Add rim light prompt with specific position description and color --- .../lighting/LightingEffectModal.tsx | 33 +++++++++++++++++-- src/store/execution/nanoBananaExecutor.ts | 5 +-- src/utils/__tests__/lighting.test.ts | 4 +-- src/utils/lighting.ts | 14 +++++--- 4 files changed, 44 insertions(+), 12 deletions(-) diff --git a/src/components/lighting/LightingEffectModal.tsx b/src/components/lighting/LightingEffectModal.tsx index beee8f8b..ebab3071 100644 --- a/src/components/lighting/LightingEffectModal.tsx +++ b/src/components/lighting/LightingEffectModal.tsx @@ -1007,7 +1007,8 @@ export function LightingEffectModal({ }, [isOpen, value]); const modalWidthClass = settings.smartMode ? "max-w-[760px]" : "max-w-[458px]"; - const appliedPreviewImage = settings.referenceImage || sourceImage; + // 预览始终显示原始图片(sourceImage)作为打光主体,参考图不替代原图 + const appliedPreviewImage = sourceImage; const selectedPreset = useMemo( () => LIGHTING_PRESETS.find((preset) => preset.id === settings.selectedPresetId), @@ -1221,9 +1222,35 @@ export function LightingEffectModal({ 0 ? buildLightingPrompt(activeLighting) : ""; - // 智能自由模式:将打光参考图追加到图片数组,作为第二张图传给模型 - if (isSmartFreeMode(activeLighting) && activeLighting.referenceImage) { + // 打光参考图:将参考图追加到图片数组,作为第二张图传给模型 + // 原始图片始终为第一张,参考图为第二张,模型根据参考图的打光风格对原图重打光 + if (activeLighting?.enabled && activeLighting.referenceImage) { images = [...images, activeLighting.referenceImage]; } diff --git a/src/utils/__tests__/lighting.test.ts b/src/utils/__tests__/lighting.test.ts index 0a922db5..fd1d9c33 100644 --- a/src/utils/__tests__/lighting.test.ts +++ b/src/utils/__tests__/lighting.test.ts @@ -113,7 +113,7 @@ describe("lighting utilities", () => { referenceImage: "data:image/png;base64,abc", }); - expect(prompt).toContain("second image is provided"); - expect(prompt).toContain("lighting reference"); + expect(prompt).toContain("SECOND image is the lighting reference"); + expect(prompt).toContain("FIRST image"); }); }); diff --git a/src/utils/lighting.ts b/src/utils/lighting.ts index 3a4c15ac..87a9492e 100644 --- a/src/utils/lighting.ts +++ b/src/utils/lighting.ts @@ -282,8 +282,11 @@ export function buildLightingPrompt(settings?: LightingSettings | null): string beamAngleInstruction(lighting.beamAngle), ]; + // 轮廓光:在主光源之外追加第二个光源,位于主体后方 + // 颜色与主光源一致,位置由用户在球体背面拖拽决定 if (lighting.rimLight) { const rimPos = lighting.rimLightPosition ?? DEFAULT_LIGHTING_SETTINGS.rimLightPosition; + // 根据 3D 坐标判断轮廓光的水平/垂直/深度方位,生成可读描述 const rimHoriz = rimPos.x <= -0.25 ? "camera-left" : rimPos.x >= 0.25 ? "camera-right" : "centered"; const rimVert = rimPos.y <= -0.25 ? "above" : rimPos.y >= 0.25 ? "below" : "at subject level"; const rimDepth = rimPos.z <= -0.25 ? "directly behind" : rimPos.z >= 0.25 ? "in front of" : "beside"; @@ -319,7 +322,8 @@ export function buildOptimizedLightingPrompt( ): string { const lightingPrompt = buildLightingPrompt(settings); const userPrompt = (basePrompt ?? "").trim(); - const smartRef = isSmartFreeMode(settings); + const lighting = normalizeLightingSettings(settings); + const hasRefImage = !!lighting.referenceImage; // 打光未启用 → 原样返回用户提示词 if (!lightingPrompt) { @@ -332,7 +336,7 @@ export function buildOptimizedLightingPrompt( // 严格约束:锁定人物身份、构图、场景,只允许改变光照 const constraints = [ - "STRICT image-to-image relighting task: use the uploaded image as the locked source image.", + "STRICT image-to-image relighting task: the FIRST image is the source image to be relit — do not modify its content.", 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.", @@ -342,10 +346,10 @@ export function buildOptimizedLightingPrompt( "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.", ]; - // 智能参考图模式:告知模型第二张图片是打光参考 - if (smartRef) { + // 打光参考图:第二张图片是打光参考,分析其光照风格应用到原图 + if (hasRefImage) { constraints.push( - "If a second image is provided, use it as the lighting reference — match its lighting style, direction, color temperature, and mood." + "The SECOND image is the lighting reference — analyze its lighting style, direction, color temperature, shadows, and mood, then apply the same lighting to the FIRST image." ); }