mcp-relight-harmonize 1.0.2 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,40 +1,60 @@
1
1
  ---
2
2
  name: mcp-relight-harmonize
3
3
  description: >-
4
- Execute end-to-end optical image profiling, physically-grounded relighting variations, contact-aware composite harmonization,
5
- and precision diffusion prompt synthesis targeting GPT Image and Nano Banana. Use when inspecting lighting angles or color temperature,
4
+ Execute end-to-end 6-layer optical image profiling, physically-grounded relighting variations, contact-aware composite harmonization,
5
+ and precision diffusion prompt synthesis compatible with Any Image Generator Model (optimized for GEMINI Nano Banana in Antigravity). Use when inspecting lighting angles or color temperature,
6
6
  generating alternative illumination schemes (Ambient, Dramatic, Rim, Mood), blending foreground cutouts into backgrounds,
7
7
  or drafting photorealistic prompts.
8
8
  ---
9
9
 
10
10
  # `mcp-relight-harmonize` Skill Specification
11
11
 
12
- Created by **MarwanDevSpace**, this skill empowers the agent to execute physical lighting analysis, generate relighted variations, perform seamless composite harmonization, and craft production diffusion prompts tailored for **GPT Image** and **Nano Banana**.
12
+ Created by **MarwanDevSpace**, this skill empowers the agent to execute physical lighting analysis, decompose images into 6 visual layers in `Layers/`, generate relighted variations, perform seamless composite harmonization, and craft production diffusion prompts tailored for **Any Image Generator Model** (specifically optimized for **GEMINI Nano Banana** in **Google Antigravity**).
13
+
14
+ > [!NOTE]
15
+ > **Antigravity Recommended**: This skill is optimized to work with **any Image Generator**, and is **strongly preferred inside Google Antigravity** where native desktop image generation (`generate_image`) with **GEMINI Nano Banana** can immediately execute the desired modifications.
13
16
 
14
17
  ---
15
18
 
16
19
  ## 1. When to Activate This Skill
17
20
 
18
21
  Activate this skill whenever the user asks to:
19
- 1. **Analyze an image's lighting:** Determine Correlated Color Temperature (CCT in Kelvin), lighting direction vector, azimuth/elevation angles, contrast ratio, or surface roughness.
20
- 2. **Relight an image:** Produce 4 physically-grounded lighting variants (`Ambient`, `Dramatic`, `Rim`, `Mood`).
22
+ 1. **Decompose & Analyze an image's lighting:** Generate 6 analytical layers into `Layers/` and determine CCT (Kelvin), lighting direction, azimuth/elevation angles, contrast ratio, or surface roughness.
23
+ 2. **Relight an image:** Produce physically-grounded lighting variants (`Ambient`, `Dramatic`, `Rim`, `Mood`).
21
24
  3. **Composite/Harmonize an image:** Place a cutout foreground onto a background scene, matching ambient color statistics and adding physically grounded contact shadows.
22
- 4. **Generate `/prompt` for diffusion:** Create exact physical lighting and detail enhancement prompts for **GPT Image** (DALL-E 3 / GPT-4o) and **Nano Banana** with calibrated denoising strength (`0.35 - 0.45`).
25
+ 4. **Generate `/prompt` for diffusion:** Create exact physical lighting and detail enhancement prompts for **Any Image Generator** / **GEMINI Nano Banana** with calibrated denoising strength (`0.35 - 0.45`).
23
26
 
24
27
  ---
25
28
 
26
29
  ## 2. Core Workflows & Execution Procedures
27
30
 
28
- ### Workflow A: Optical Profiling
31
+ ### Workflow A: Optical Profiling & 6-Layer Decomposition
29
32
  Before modifying or inpainting an image, extract its optical geometry:
30
- 1. Call tool `analyze_optical_profile({ image_path: "<path_to_image>" })`.
31
- 2. Review the resulting `OpticalProfileReport`:
32
- - **`colorTemperatureKelvin`**: e.g., 3200K tungsten vs 6500K daylight.
33
- - **`lightingAngles`**: `azimuthDeg` (horizontal angle) and `elevationDeg` (vertical angle).
33
+ 1. Call tool `analyze_optical_profile({ image_path: "<path_to_image>", extract_layers: true })` or run:
34
+ `python scripts/extract_layers.py --image <path> [--intent "<intent>"]`
35
+ 2. Python decomposes the image into 6 visual layers inside `Layers/`:
36
+ - `01_highlights.png` (Highlights / Specular Zones)
37
+ - `02_shadows.png` (Shadows / Low-Key Zones)
38
+ - `03_ambient_occlusion.png` (Deep Ambient Occlusion & Ground Contact)
39
+ - `04_edges.png` (Sobel High-Frequency Contours)
40
+ - `05_depth_normals.png` (3D Surface Normal Gradient Field)
41
+ - `06_chroma_saturation.png` (Chrominance & Saturation Distribution)
42
+ 3. Review the resulting `OpticalProfileReport` and dynamic `Layer.md`:
43
+ - **`colorTemperatureKelvin`**: e.g., 3200K tungsten vs 5500K daylight.
44
+ - **`lightingAngles`**: `azimuthDeg` and `elevationDeg`.
34
45
  - **`luminanceDynamics.contrastRatio`**: Key-to-fill ratio.
35
- 3. Use these physical parameters to inform subsequent relighting or prompt writing.
36
-
37
- ### Workflow B: Generating 4 Relit Variations
46
+ - **`aoCoveragePct`**: Contact shadow presence vs floating subject.
47
+ 4. Store findings in memory / CoT to guide subsequent generation.
48
+
49
+ ### Workflow B: Interactive Decision & Direct Image Generation
50
+ 1. Address the user with the mandatory prompt:
51
+ **"ماذا تريد من تعديل؟"**
52
+ accompanied by tailored recommendations derived strictly from the image's detected optical profile.
53
+ 2. Upon user selection or intent:
54
+ - Synthesize the photorealistic prompt for the **Image Generator**.
55
+ - **Inside Google Antigravity**: Directly trigger `generate_image` using the synthesized prompt to produce the modified image on the spot!
56
+
57
+ ### Workflow C: Physical Relighting Variations
38
58
  To produce visual lighting alternatives on disk:
39
59
  1. Call tool `generate_relight_variations({ image_path: "<path_to_image>", target_lighting: "All" })`.
40
60
  2. The tool produces 4 files in `OUTPUT_CACHE_DIR`:
@@ -42,44 +62,36 @@ To produce visual lighting alternatives on disk:
42
62
  - `*_relight_dramatic.png` (-1.5 EV shadow crush, top-left directional chiaroscuro).
43
63
  - `*_relight_rim.png` (+1.2 EV high-pass edge halo, cool cyan perimeter).
44
64
  - `*_relight_mood.png` (3200K tungsten amber shift, highlight bloom diffusion).
45
- 3. Present the resulting artifact paths to the user with their mathematical adjustment logs.
46
65
 
47
- ### Workflow C: Composite Harmonization
66
+ ### Workflow D: Composite Harmonization
48
67
  To blend a subject or product into a new background:
49
68
  1. Call tool `harmonize_composite({ foreground_path: "<fg>", background_path: "<bg>", blend_mode: "seamless" })`.
50
- 2. The engine executes:
51
- - **Reinhard Color Statistics Transfer:** Normalizes foreground means and deviations to match background in Ruderman $l\alpha\beta$ space.
52
- - **Contact Shadow Synthesis:** Projects an elliptical Gaussian shadow beneath the subject's lowest contact plane.
53
- - **Smooth Blending:** Eliminates boundary halo artifacts.
54
- 3. Return the composite image file path.
69
+ 2. The engine executes Reinhard Color Statistics Transfer in Ruderman $l\alpha\beta$ space, applies Ground Contact Shadows, and smooths boundaries.
55
70
 
56
- ### Workflow D: Diffusion Prompt Synthesis (`/prompt`)
71
+ ### Workflow E: Diffusion Prompt Synthesis (`/prompt`)
57
72
  To craft targeted generative prompts:
58
- 1. Call tool `synthesize_diffusion_prompt({ image_path: "<path>", user_intent: "<intent>", target_model: "gpt_image" | "nano_banana" })`.
59
- 2. Extract the two specialized prompts:
60
- - **`enhancementPrompt`**: Upgrades micro-surface textures, pore fidelity, lens sharpness, and subsurface scattering.
61
- - **`relightingPrompt`**: Injects exact lighting angles, Kelvin temperature, volumetric dust rays, and contact shadows.
62
- 3. For **GPT Image**: outputs natural descriptive photography directives (85mm f/2.0, physical illumination).
63
- 4. For **Nano Banana**: outputs dense optical tokens (roughness index, raytraced bounce, ground contact shadow, azimuth).
64
-
65
- ### Workflow E: Output Cache Inspection
66
- To inspect or discover previously generated relight variations or composite outputs:
67
- 1. Call tool `list_cached_variations({ cache_dir?: "<path>" })`.
68
- 2. Inspect `files` list containing filename, full path, byte size, and last modified timestamp for all output artifacts.
73
+ 1. Call tool `synthesize_diffusion_prompt({ image_path: "<path>", user_intent: "<intent>", target_model: "universal" | "nano_banana" })`.
74
+ 2. Extract the specialized prompts:
75
+ - **`enhancementPrompt`**: Micro-surface textures, pore fidelity, lens sharpness, and subsurface scattering.
76
+ - **`relightingPrompt`**: Exact lighting angles, Kelvin temperature, volumetric dust rays, and contact shadows.
77
+
78
+ ### Workflow F: Output Cache Inspection
79
+ 1. Call tool `list_cached_variations({ cache_dir?: "<path>" })` to inspect generated artifacts.
69
80
 
70
81
  ---
71
82
 
72
83
  ## 3. Reference Documentation
73
84
 
74
- For in-depth mathematical formulations and model guides:
75
85
  - [Optical Mathematics & Formulations](./references/optical_math.md)
76
86
  - [Tool Orchestration & Chaining Standards](./references/tool_orchestration.md)
77
- - [GPT Image & Nano Banana Guide](./references/diffusion_guide.md)
87
+ - [Image Generator & GEMINI Nano Banana Guide](./references/diffusion_guide.md)
78
88
 
79
89
  ---
80
90
 
81
91
  ## 4. Executable Helper Scripts
82
92
 
93
+ - Execute 6-layer optical extraction:
94
+ `python scripts/extract_layers.py --image <image_path> [--intent "<intent>"]`
83
95
  - Execute full end-to-end analysis & prompt synthesis from CLI:
84
96
  `node .agents/skills/mcp-relight-harmonize/scripts/run_pipeline.js <image_path>`
85
97
  - Verify server health and tool registration:
@@ -1,22 +1,23 @@
1
- # Diffusion Prompt Engineering Guide: GPT Image & Nano Banana
1
+ # Image Generator Prompt Engineering Guide: Universal & GEMINI Nano Banana
2
2
 
3
- This document maps physical optical metrics into specialized prompt directives for **GPT Image** and **Nano Banana**.
3
+ This document maps physical optical metrics into specialized prompt directives compatible with **Any Image Generator Model**, with specific optimization for **GEMINI Nano Banana** in **Google Antigravity**.
4
4
 
5
5
  ---
6
6
 
7
- ## 1. Target Engine Overview
7
+ ## 1. Target Engine Paradigms
8
8
 
9
- | Parameter | GPT Image (DALL-E 3 / GPT-4o) | Nano Banana (Optical Inpainting Engine) |
9
+ | Parameter | Universal Image Generator (Natural Studio) | GEMINI Nano Banana (Optical Shaders) |
10
10
  |---|---|---|
11
- | **Prompt Paradigm** | Natural language, photographic directives | Dense tokenized optical descriptors |
11
+ | **Prompt Paradigm** | Natural language, photographic studio directives | Dense tokenized optical descriptors & shaders |
12
12
  | **Optics Formulation** | 85mm prime lens f/2.0, authentic falloff | Micro-surface specular roughness, normal angle |
13
13
  | **Contact Shadows** | "physically-based contact shadows at base" | "ground contact shadow, ambient occlusion caster" |
14
14
  | **Volumetrics** | "subtle volumetric dust rays in air" | "volumetric raytraced bounce" |
15
15
  | **Denoising Default** | `0.38` | `0.38` (subtle) to `0.42` (dramatic) |
16
+ | **Recommended Environment** | Any diffusion generator | **Google Antigravity (`generate_image`)** |
16
17
 
17
18
  ---
18
19
 
19
- ## 2. GPT Image Prompt Architecture
20
+ ## 2. Universal Image Generator Prompt Architecture
20
21
 
21
22
  ### Enhancement Prompt Template:
22
23
  ```text
@@ -34,7 +35,7 @@ authentic photometric falloff, [User Intent].
34
35
 
35
36
  ---
36
37
 
37
- ## 3. Nano Banana Prompt Architecture
38
+ ## 3. GEMINI Nano Banana Prompt Architecture
38
39
 
39
40
  ### Enhancement Prompt Template:
40
41
  ```text
@@ -50,7 +51,8 @@ denoising 0.38, light_azimuth_[Azimuth]deg, [User Intent]
50
51
  ```
51
52
 
52
53
  ### Recommended Parameters:
53
- - `model`: `"nano-banana-optical-v1"`
54
+ - `model`: `"gemini-nano-banana"`
55
+ - `target_environment`: `"Google Antigravity"`
54
56
  - `denoising_strength`: `0.38`
55
57
  - `guidance_scale`: `4.5`
56
58
  - `steps`: `32`
@@ -0,0 +1,412 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ MarwanDevSpace - Dynamic 6-Layer Optical Decomposition & Intelligence Engine
4
+ Extracts 6 analytical visual layers into Layers/ directory:
5
+ 1. 01_highlights.png - طبقة الألوان الفاتحة (Highlights / Specular Zones)
6
+ 2. 02_shadows.png - طبقة الألوان الغامقة (Shadows / Low-Key Zones)
7
+ 3. 03_ambient_occlusion.png - طبقة الظل العالي (Deep Ambient Occlusion & Ground Contact)
8
+ 4. 04_edges.png - طبقة الحواف (Sobel High-Frequency Contours)
9
+ 5. 05_depth_normals.png - طبقة العمق (3D Surface Normal Gradient Field)
10
+ 6. 06_chroma_saturation.png - طبقة الألوان والتشبع والبكسلات (Chroma & Saturation Distribution)
11
+
12
+ Generates dynamic, on-demand Layer.md reports and physical generative prompts
13
+ specifically targeting Universal Image Generator and Nano Banana without static boilerplate.
14
+ """
15
+
16
+ import os
17
+ import sys
18
+ import argparse
19
+ import json
20
+ from typing import Optional, Dict, Any, List
21
+ import numpy as np
22
+ from PIL import Image
23
+
24
+ def calculate_cct(r_mean: float, g_mean: float, b_mean: float) -> float:
25
+ """Calculates Correlated Color Temperature (Kelvin) using CIE 1931 xy and McCamy formulation."""
26
+ r_norm = min(max(r_mean / 255.0, 0.0), 1.0)
27
+ g_norm = min(max(g_mean / 255.0, 0.0), 1.0)
28
+ b_norm = min(max(b_mean / 255.0, 0.0), 1.0)
29
+
30
+ # Gamma linearization (sRGB -> linear RGB)
31
+ r_lin = ((r_norm + 0.055) / 1.055) ** 2.4 if r_norm > 0.04045 else r_norm / 12.92
32
+ g_lin = ((g_norm + 0.055) / 1.055) ** 2.4 if g_norm > 0.04045 else g_norm / 12.92
33
+ b_lin = ((b_norm + 0.055) / 1.055) ** 2.4 if b_norm > 0.04045 else b_norm / 12.92
34
+
35
+ # CIE XYZ matrix transformation
36
+ X = 0.4124564 * r_lin + 0.3575761 * g_lin + 0.1804375 * b_lin
37
+ Y = 0.2126729 * r_lin + 0.7151522 * g_lin + 0.0721750 * b_lin
38
+ Z = 0.0193339 * r_lin + 0.1191920 * g_lin + 0.9503041 * b_lin
39
+
40
+ total = X + Y + Z
41
+ if total <= 1e-7:
42
+ return 6500.0
43
+
44
+ x = X / total
45
+ y = Y / total
46
+ denom = 0.1858 - y
47
+ if abs(denom) < 1e-6:
48
+ denom = 1e-6 if denom >= 0 else -1e-6
49
+
50
+ n = (x - 0.3320) / denom
51
+ cct = 449.0 * (n ** 3) + 3525.0 * (n ** 2) + 6823.3 * n + 5520.33
52
+ return float(np.clip(cct, 1500.0, 20000.0))
53
+
54
+ def build_dynamic_diagnostics(
55
+ cct_kelvin: float,
56
+ azimuth_deg: float,
57
+ elevation_deg: float,
58
+ hl_coverage_pct: float,
59
+ shadow_coverage_pct: float,
60
+ ao_coverage_pct: float,
61
+ mean_gradient: float,
62
+ mean_saturation: float,
63
+ user_intent: Optional[str] = None
64
+ ) -> Dict[str, Any]:
65
+ """Dynamically analyzes layer metrics to produce targeted findings, customized suggestions, and diffusion prompts."""
66
+ findings: List[str] = []
67
+ tailored_options: List[str] = []
68
+
69
+ # 1. Thermal & Chromatic Diagnostics
70
+ if cct_kelvin < 3800:
71
+ thermal_desc = f"طيف لوني دافئ جداً ({round(cct_kelvin)}K - Tungsten/Golden Hour)"
72
+ findings.append(f"🔥 **انحياز طيفي دافئ**: الصورة مسجلة بحرارة `{round(cct_kelvin)}K` مع غلبة للأطياف البرتقالية والصفراء في القنوات اللونية.")
73
+ tailored_options.append(f"موازنة حرارة الألوان من `{round(cct_kelvin)}K` إلى 5500K نهارية محايدة لإزالة المسحة الكهرمانية.")
74
+ elif cct_kelvin > 6500:
75
+ thermal_desc = f"طيف لوني بارد ({round(cct_kelvin)}K - Cool Atmospheric Skylight)"
76
+ findings.append(f"❄️ **انحياز طيفي بارد**: الصورة مسجلة بحرارة `{round(cct_kelvin)}K` مع غلبة للزرقة والارتداد السماوي البارد.")
77
+ tailored_options.append(f"تدفئة المشهد الضوئي برفع حرارة الألوان إلى 3200K (Mood Tungsten) أو 4500K سينمائي.")
78
+ else:
79
+ thermal_desc = f"طيف نهاري متوازن ({round(cct_kelvin)}K - Balanced Daylight)"
80
+ findings.append(f"☀️ **حرارة ألوان متوازنة**: حرارة الألوان عند `{round(cct_kelvin)}K` تماثل الإضاءة الاستوديوية المحايدة.")
81
+
82
+ # 2. Lighting Angles & Dynamic Contrast
83
+ light_dir = "يمين" if 0 <= azimuth_deg < 90 or 270 <= azimuth_deg <= 360 else "يسار"
84
+ light_vert = "علوية" if elevation_deg > 50 else "أفقية جانبية"
85
+ findings.append(f"💡 **توجيه الإضاءة الرئيسية**: زاوية السمت `{round(azimuth_deg)}°` (إضاءة من جهة ال{light_dir}) مع زاوية ارتفاع `{round(elevation_deg)}°` ({light_vert}).")
86
+
87
+ if shadow_coverage_pct > 35.0 and hl_coverage_pct > 15.0:
88
+ findings.append(f"⚡ **تباين درامي عالي (Chiaroscuro)**: كثافة الظلال `{round(shadow_coverage_pct, 1)}%` مع سعة مناطق ساطعة `{round(hl_coverage_pct, 1)}%` تدل على تباين قوي بين المفتاح الضوئي والملء.")
89
+ tailored_options.append("تنعيم التباين ورفع تفاصيل الظلال المغلقة عبر وضع الإضاءة المحيطية (Ambient Fill +0.8 EV).")
90
+ elif shadow_coverage_pct < 12.0 and hl_coverage_pct < 12.0:
91
+ findings.append("🌫️ **إضاءة منبسطة ناعمة (Flat Diffuse)**: تباين منخفض وغياب للمناطق الساطعة الحادة، ما يعطي مظهراً هادئاً.")
92
+ tailored_options.append("إضافة عمق درامي وزيادة التباين بنمط Chiaroscuro عالي التحديد مع إضاءة اتجاهية بارزة.")
93
+
94
+ # 3. Grounding & Ambient Occlusion
95
+ if ao_coverage_pct < 2.0:
96
+ findings.append(f"⚠️ **فقدان الارتكاز الأرضي (Lack of Grounding)**: نسبة الظلال التلامسية العميقة `{round(ao_coverage_pct, 2)}%` تكاد تنعدم، مما قد يظهر العنصر كأنه طافٍ في الفراغ.")
97
+ tailored_options.append(f"بناء وتثبيت ظل تلامسي أرضي فيزيائي (Contact Shadow Footprint) أسفل أدنى نقطة ارتكاز.")
98
+ else:
99
+ findings.append(f"⚓ **ارتكاز أرضي متماسك**: نسبة ظلال التلامس والانغلاق الموضعي `{round(ao_coverage_pct, 1)}%` تؤمن التصاقاً بصرياً طبيعياً بالأرضية.")
100
+
101
+ # 4. Surface Micro-textures & Edges
102
+ if mean_gradient > 0.03:
103
+ findings.append(f"🔍 **تفاصيل سطحية دقيقة وحواف حادة**: متوسط تباين سوبل `{round(mean_gradient, 4)}` يشير إلى وفرة في التفاصيل المجهرية والأنسجة السطحية الواضحة.")
104
+ else:
105
+ findings.append(f"🎨 **حواف ناعمة وسطح انسيابي**: متوسط تدرج سوبل `{round(mean_gradient, 4)}` يشير إلى مساحات ناعمة متصلة ومناسبة للتنعيم وإعادة التوزيع.")
106
+
107
+ # 5. Chroma / Saturation
108
+ if mean_saturation > 0.40:
109
+ findings.append(f"🌈 **تشبع لوني مكثف**: متوسط التشبع `{round(mean_saturation, 3)}` يظهر كثافة لونية حيوية في البكسلات.")
110
+ else:
111
+ findings.append(f"🔘 **لوحة لونية هادئة أو معتدلة**: متوسط التشبع `{round(mean_saturation, 3)}` يعكس تدرجات رصينة غير مبالغ بها.")
112
+
113
+ # Add custom intent option if provided
114
+ if user_intent:
115
+ tailored_options.insert(0, f"تطبيق طلبك المخصص فوراً: \"{user_intent}\" بتطابق فيزيائي دقيق مع بيانات الطبقات الست.")
116
+ else:
117
+ tailored_options.append("إعادة إضاءة بنمط هالة الحواف (Rim Light Halo +1.2 EV) لإبراز حدود المجسم وعزله عن الخلفية.")
118
+ tailored_options.append("دمج العنصر في خلفية جديدة مع مطابقة إحصائيات الألوان ودرجة الحرارة وظلال الارتكاز.")
119
+
120
+ # Dynamic Generative Prompts tailored to exact metrics
121
+ intent_clause = f", {user_intent}" if user_intent else ""
122
+ universal_prompt = (
123
+ f"A master-quality studio photograph{intent_clause}, calibrated optical lighting at {round(azimuth_deg)}° azimuth "
124
+ f"and {round(elevation_deg)}° elevation, authentic {round(cct_kelvin)}K color temperature balance, "
125
+ f"physically-grounded ambient occlusion contact shadows firmly anchoring the base plane, "
126
+ f"crisp micro-surface geometry (Sobel roughness index {round(mean_gradient, 3)}), "
127
+ f"smooth luminance falloff and authentic subsurface scattering, 85mm prime lens f/2.0."
128
+ )
129
+
130
+ nano_banana_prompt = (
131
+ f"optics relight, cct {round(cct_kelvin)}K, light vector azimuth {round(azimuth_deg)} deg elevation {round(elevation_deg)} deg, "
132
+ f"surface roughness {round(mean_gradient, 4)}, saturation index {round(mean_saturation, 3)}, "
133
+ f"ground contact occlusion caster, volumetric photon bounce, high-key rim highlight accents, "
134
+ f"denoising strength 0.38{intent_clause}"
135
+ )
136
+
137
+ return {
138
+ "thermalDescription": thermal_desc,
139
+ "findings": findings,
140
+ "tailoredOptions": tailored_options,
141
+ "universalImagePrompt": universal_prompt,
142
+ "nanoBananaPrompt": nano_banana_prompt,
143
+ }
144
+
145
+ def extract_layers(image_path: str, output_dir: str = "Layers", user_intent: Optional[str] = None) -> Dict[str, Any]:
146
+ abs_image_path = os.path.abspath(image_path)
147
+ if not os.path.exists(abs_image_path):
148
+ raise FileNotFoundError(f"Source image not found: {abs_image_path}")
149
+
150
+ os.makedirs(output_dir, exist_ok=True)
151
+ abs_output_dir = os.path.abspath(output_dir)
152
+
153
+ img = Image.open(abs_image_path).convert("RGB")
154
+ rgb = np.array(img, dtype=np.float32)
155
+ height, width, _ = rgb.shape
156
+ num_pixels = width * height
157
+
158
+ # Luminance channel Y
159
+ lum = 0.2126 * rgb[:, :, 0] + 0.7152 * rgb[:, :, 1] + 0.0722 * rgb[:, :, 2]
160
+ norm_lum = lum / 255.0
161
+
162
+ # 1. طبقة الألوان الفاتحة (Highlights / Specular Zones)
163
+ hl_mask = np.clip((lum - 170.0) / 75.0, 0.0, 1.0)
164
+ hl_layer = rgb * hl_mask[:, :, np.newaxis]
165
+ hl_coverage_pct = float((np.sum(lum > 170.0) / num_pixels) * 100.0)
166
+ hl_path = os.path.join(abs_output_dir, "01_highlights.png")
167
+ Image.fromarray(np.uint8(np.clip(hl_layer, 0, 255))).save(hl_path)
168
+
169
+ # 2. طبقة الألوان الغامقة (Shadows / Low-Key Zones)
170
+ shadow_mask = np.clip((95.0 - lum) / 80.0, 0.0, 1.0)
171
+ shadow_layer = rgb * shadow_mask[:, :, np.newaxis]
172
+ shadow_coverage_pct = float((np.sum(lum < 85.0) / num_pixels) * 100.0)
173
+ shadow_path = os.path.join(abs_output_dir, "02_shadows.png")
174
+ Image.fromarray(np.uint8(np.clip(shadow_layer, 0, 255))).save(shadow_path)
175
+
176
+ # 3. طبقة الظل العالي (Deep Ambient Occlusion & Contact Umbra)
177
+ ao_mask = np.clip((40.0 - lum) / 40.0, 0.0, 1.0)
178
+ ao_visual = np.zeros((height, width, 3), dtype=np.float32)
179
+ ao_visual[:, :, 0] = (1.0 - ao_mask) * 230.0
180
+ ao_visual[:, :, 1] = (1.0 - ao_mask) * 240.0 + (ao_mask * 15.0)
181
+ ao_visual[:, :, 2] = (1.0 - ao_mask) * 255.0 + (ao_mask * 30.0)
182
+ ao_coverage_pct = float((np.sum(lum < 35.0) / num_pixels) * 100.0)
183
+ ao_path = os.path.join(abs_output_dir, "03_ambient_occlusion.png")
184
+ Image.fromarray(np.uint8(np.clip(ao_visual, 0, 255))).save(ao_path)
185
+
186
+ # 4. طبقة الحواف (Sobel Gradient Contours)
187
+ sobel_x = np.array([[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]], dtype=np.float32)
188
+ sobel_y = np.array([[-1, -2, -1], [0, 0, 0], [1, 2, 1]], dtype=np.float32)
189
+ padded_lum = np.pad(norm_lum, 1, mode="edge")
190
+ gx = np.zeros((height, width), dtype=np.float32)
191
+ gy = np.zeros((height, width), dtype=np.float32)
192
+ for i in range(3):
193
+ for j in range(3):
194
+ gx += sobel_x[i, j] * padded_lum[i : i + height, j : j + width]
195
+ gy += sobel_y[i, j] * padded_lum[i : i + height, j : j + width]
196
+
197
+ edge_mag = np.sqrt(gx ** 2 + gy ** 2)
198
+ edge_vis = np.clip(edge_mag * 4.0 * 255.0, 0, 255)
199
+ mean_gradient = float(np.mean(edge_mag))
200
+ edge_path = os.path.join(abs_output_dir, "04_edges.png")
201
+ Image.fromarray(np.uint8(edge_vis)).save(edge_path)
202
+
203
+ # 5. طبقة العمق (Depth / 3D Normal Vector Field)
204
+ scale = 6.0
205
+ nx = -gx * scale
206
+ ny = -gy * scale
207
+ nz = np.ones((height, width), dtype=np.float32)
208
+ mag = np.sqrt(nx ** 2 + ny ** 2 + nz ** 2) + 1e-7
209
+ nx_norm = nx / mag
210
+ ny_norm = ny / mag
211
+ nz_norm = nz / mag
212
+
213
+ normal_rgb = np.zeros((height, width, 3), dtype=np.float32)
214
+ normal_rgb[:, :, 0] = (nx_norm * 0.5 + 0.5) * 255.0
215
+ normal_rgb[:, :, 1] = (ny_norm * 0.5 + 0.5) * 255.0
216
+ normal_rgb[:, :, 2] = (nz_norm * 0.5 + 0.5) * 255.0
217
+ depth_path = os.path.join(abs_output_dir, "05_depth_normals.png")
218
+ Image.fromarray(np.uint8(np.clip(normal_rgb, 0, 255))).save(depth_path)
219
+
220
+ lum_excess = np.maximum(lum - np.mean(lum), 0.0) ** 2
221
+ sum_w = np.sum(lum_excess) + 1e-7
222
+ avg_lx = np.sum(nx_norm * lum_excess) / sum_w
223
+ avg_ly = np.sum(ny_norm * lum_excess) / sum_w
224
+ avg_lz = np.sum(nz_norm * lum_excess) / sum_w
225
+ l_mag = np.sqrt(avg_lx**2 + avg_ly**2 + avg_lz**2) + 1e-7
226
+ avg_lx, avg_ly, avg_lz = avg_lx / l_mag, avg_ly / l_mag, avg_lz / l_mag
227
+ azimuth_deg = (np.degrees(np.arctan2(-avg_ly, avg_lx)) + 360.0) % 360.0
228
+ elevation_deg = np.clip(np.degrees(np.arcsin(np.clip(avg_lz, -1.0, 1.0))), 0.0, 90.0)
229
+
230
+ # 6. طبقة الألوان والتشبع (Chrominance / Saturation Distribution)
231
+ max_c = np.maximum(np.maximum(rgb[:, :, 0], rgb[:, :, 1]), rgb[:, :, 2])
232
+ min_c = np.minimum(np.minimum(rgb[:, :, 0], rgb[:, :, 1]), rgb[:, :, 2])
233
+ chroma = max_c - min_c
234
+ sat = np.zeros_like(chroma)
235
+ non_zero = max_c > 1e-4
236
+ sat[non_zero] = chroma[non_zero] / max_c[non_zero]
237
+
238
+ sat_vis = np.zeros((height, width, 3), dtype=np.float32)
239
+ sat_vis[:, :, 0] = np.clip(sat * 255.0 * 1.5, 0, 255)
240
+ sat_vis[:, :, 1] = np.clip((1.0 - np.abs(sat - 0.5) * 2.0) * 220.0, 0, 255)
241
+ sat_vis[:, :, 2] = np.clip((1.0 - sat) * 200.0, 0, 255)
242
+ mean_saturation = float(np.mean(sat))
243
+ chroma_path = os.path.join(abs_output_dir, "06_chroma_saturation.png")
244
+ Image.fromarray(np.uint8(sat_vis)).save(chroma_path)
245
+
246
+ # Correlated Color Temperature
247
+ mean_r = float(np.mean(rgb[:, :, 0]))
248
+ mean_g = float(np.mean(rgb[:, :, 1]))
249
+ mean_b = float(np.mean(rgb[:, :, 2]))
250
+ cct_kelvin = calculate_cct(mean_r, mean_g, mean_b)
251
+
252
+ # Build Dynamic Diagnostics based purely on calculated data
253
+ diag = build_dynamic_diagnostics(
254
+ cct_kelvin=cct_kelvin,
255
+ azimuth_deg=azimuth_deg,
256
+ elevation_deg=elevation_deg,
257
+ hl_coverage_pct=hl_coverage_pct,
258
+ shadow_coverage_pct=shadow_coverage_pct,
259
+ ao_coverage_pct=ao_coverage_pct,
260
+ mean_gradient=mean_gradient,
261
+ mean_saturation=mean_saturation,
262
+ user_intent=user_intent
263
+ )
264
+
265
+ report = {
266
+ "sourceImage": abs_image_path,
267
+ "layersDirectory": abs_output_dir,
268
+ "dimensions": [width, height],
269
+ "opticalMetrics": {
270
+ "cctKelvin": round(cct_kelvin, 1),
271
+ "dominantLightVector": [round(float(avg_lx), 4), round(float(avg_ly), 4), round(float(avg_lz), 4)],
272
+ "lightingAngles": {
273
+ "azimuthDeg": round(float(azimuth_deg), 1),
274
+ "elevationDeg": round(float(elevation_deg), 1),
275
+ },
276
+ "meanLuminance": round(float(np.mean(lum)), 2),
277
+ "meanSaturation": round(mean_saturation, 3),
278
+ "edgeRoughness": round(mean_gradient, 4),
279
+ },
280
+ "diagnostics": diag,
281
+ "layers": {
282
+ "01_highlights": {
283
+ "file": "01_highlights.png",
284
+ "path": hl_path,
285
+ "description": "طبقة الألوان الفاتحة - High-key Luminance & Specular Zones",
286
+ "coveragePct": round(hl_coverage_pct, 2),
287
+ },
288
+ "02_shadows": {
289
+ "file": "02_shadows.png",
290
+ "path": shadow_path,
291
+ "description": "طبقة الألوان الغامقة - Low-key & Shadow Zones",
292
+ "coveragePct": round(shadow_coverage_pct, 2),
293
+ },
294
+ "03_ambient_occlusion": {
295
+ "file": "03_ambient_occlusion.png",
296
+ "path": ao_path,
297
+ "description": "طبقة الظل العالي - High Contact Shadows & Ground Occlusion",
298
+ "coveragePct": round(ao_coverage_pct, 2),
299
+ },
300
+ "04_edges": {
301
+ "file": "04_edges.png",
302
+ "path": edge_path,
303
+ "description": "طبقة الحواف - Sobel High-Frequency Gradient Contours",
304
+ "meanGradient": round(mean_gradient, 4),
305
+ },
306
+ "05_depth_normals": {
307
+ "file": "05_depth_normals.png",
308
+ "path": depth_path,
309
+ "description": "طبقة العمق - 3D Surface Normal Gradient Field (RGB Tangent Map)",
310
+ "dominantAzimuth": round(float(azimuth_deg), 1),
311
+ "dominantElevation": round(float(elevation_deg), 1),
312
+ },
313
+ "06_chroma_saturation": {
314
+ "file": "06_chroma_saturation.png",
315
+ "path": chroma_path,
316
+ "description": "طبقة الألوان والتشبع والبكسلات - Chrominance & Saturation Distribution",
317
+ "meanSaturation": round(mean_saturation, 3),
318
+ },
319
+ },
320
+ }
321
+
322
+ # Format findings and tailored suggestions dynamically
323
+ findings_rendered = "\n\n".join([f"{item}" for item in diag["findings"]])
324
+ options_rendered = "\n".join([f"- **خيار {idx + 1}**: {opt}" for idx, opt in enumerate(diag["tailoredOptions"])])
325
+
326
+ user_intent_header = f"\n**الهدف المخصص المطلوب**: `{user_intent}`\n" if user_intent else ""
327
+
328
+ layer_md_path = os.path.join(abs_output_dir, "Layer.md")
329
+ layer_md_content = f"""# تقرير الطبقات التحليلية الست (Layer.md)
330
+ **مصدر الصورة**: `{abs_image_path}`
331
+ **أبعاد الصورة**: `{width}x{height}` بكسل
332
+ **التصنيف الضوئي المكتشف**: `{diag['thermalDescription']}`
333
+ **زاوية الإضاءة المسجلة**: سمت `{round(azimuth_deg)}°` | ارتفاع `{round(elevation_deg)}°`{user_intent_header}
334
+
335
+ ---
336
+
337
+ ## 1. فهرس الطبقات التحليلية الست (The 6 Extracted Layers)
338
+
339
+ | # | اسم الطبقة | الملف المولد | القراءة الفيزيائية المكتشفة |
340
+ |---|---|---|---|
341
+ | **1** | **طبقة الألوان الفاتحة (Highlights)** | [`01_highlights.png`](file:///{hl_path.replace(os.sep, '/')}) | نسبة تغطية الأضواء الساطعة: `{round(hl_coverage_pct, 2)}%` |
342
+ | **2** | **طبقة الألوان الغامقة (Shadows)** | [`02_shadows.png`](file:///{shadow_path.replace(os.sep, '/')}) | مساحة الظلال والمناطق الداكنة: `{round(shadow_coverage_pct, 2)}%` |
343
+ | **3** | **طبقة الظل العالي (Ambient Occlusion)** | [`03_ambient_occlusion.png`](file:///{ao_path.replace(os.sep, '/')}) | كثافة الارتكاز الأرضي وظلال التلامس: `{round(ao_coverage_pct, 2)}%` |
344
+ | **4** | **طبقة الحواف (Edges & Contours)** | [`04_edges.png`](file:///{edge_path.replace(os.sep, '/')}) | تباين الحواف المجهرية لسوبل: `{round(mean_gradient, 4)}` |
345
+ | **5** | **طبقة العمق (3D Depth Normals)** | [`05_depth_normals.png`](file:///{depth_path.replace(os.sep, '/')}) | توجيه التنسور المتجهي ثلاثي الأبعاد: سمت `{round(azimuth_deg)}°` |
346
+ | **6** | **طبقة الألوان والتشبع (Chroma / Saturation)** | [`06_chroma_saturation.png`](file:///{chroma_path.replace(os.sep, '/')}) | متوسط تشبع وتوزيع نقاء الألوان: `{round(mean_saturation, 3)}` |
347
+
348
+ ---
349
+
350
+ ## 2. النتائج البصرية الديناميكية المستخلصة من الصورة (Dynamic Findings)
351
+
352
+ {findings_rendered}
353
+
354
+ ---
355
+
356
+ ## 3. التوجيه التوليدي المخصص (Calibrated Prompts for Image Generators)
357
+
358
+ ### أ. النمط الوصفي الاستوديوي (Any Image Generator Model):
359
+ > "{diag['universalImagePrompt']}"
360
+
361
+ ### ب. نمط الشيدر الضوئي المكثف (GEMINI Nano Banana - Antigravity):
362
+ > "{diag['nanoBananaPrompt']}"
363
+
364
+ > 💡 **ملاحظة**: هذا التوجيه متوافق مع **أي Image Generator Model**، ويُفضل ويُوصى بشدة باستخدامه داخل **Google Antigravity** لتطبيق التعديل المباشر عبر أداة `generate_image` ومحرك **GEMINI Nano Banana**!
365
+
366
+ ---
367
+
368
+ ## 4. الخطوة التفاعلية الموجهة بالبيانات (Dynamic Interactive Decision)
369
+
370
+ **ماذا تريد من تعديل؟**
371
+ بناءً على المعطيات الضوئية المكتشفة في هذه الصورة تحديداً، إليك خيارات التعديل المقترحة:
372
+
373
+ {options_rendered}
374
+
375
+ 💬 *يمكنك أيضاً طلب أي تعديل مخصص بحرية تامة، وسيتم فوراً توليد برومبت دقيق للغاية أو إنشاء الصورة المعدلة بالكامل!*
376
+ """
377
+
378
+ with open(layer_md_path, "w", encoding="utf-8") as f:
379
+ f.write(layer_md_content)
380
+
381
+ report["layerMarkdownPath"] = layer_md_path
382
+ return report
383
+
384
+ def main():
385
+ parser = argparse.ArgumentParser(description="MarwanDevSpace Dynamic 6-Layer Optical Decomposition")
386
+ parser.add_argument("--image", "-i", required=True, help="Path to input image")
387
+ parser.add_argument("--output-dir", "-o", default="Layers", help="Output directory for the 6 layers (default: Layers)")
388
+ parser.add_argument("--intent", "-t", default=None, help="Optional user modification intent to tailor report and prompts")
389
+ parser.add_argument("--json", action="store_true", help="Print JSON report to stdout")
390
+ args = parser.parse_args()
391
+
392
+ try:
393
+ report = extract_layers(args.image, args.output_dir, user_intent=args.intent)
394
+ if args.json:
395
+ print(json.dumps(report, indent=2))
396
+ else:
397
+ diag = report["diagnostics"]
398
+ print(f"[MarwanDevSpace] 6 Analytical layers successfully extracted to: {report['layersDirectory']}")
399
+ print(f"[MarwanDevSpace] Dynamic Layer.md report created: {report['layerMarkdownPath']}")
400
+ print(f"\nOptical Profile: {diag['thermalDescription']} | Azimuth: {report['opticalMetrics']['lightingAngles']['azimuthDeg']}°")
401
+ print("\nKey Dynamic Findings:")
402
+ for f in diag["findings"][:3]:
403
+ print(f" • {f}")
404
+ print("\n[Interactive Decision Point]: ماذا تريد من تعديل؟")
405
+ for opt in diag["tailoredOptions"]:
406
+ print(f" - {opt}")
407
+ except Exception as e:
408
+ print(f"[Error] Failed to extract layers: {e}", file=sys.stderr)
409
+ sys.exit(1)
410
+
411
+ if __name__ == "__main__":
412
+ main()