import os import time import random import torch from PIL import Image, ImageDraw, ImageFont, ImageFilter, ImageOps, ImageEnhance from PIL.PngImagePlugin import PngInfo import numpy as np class LumaForgePipeline: def __init__(self, model_id="stabilityai/stable-diffusion-3.5-medium", device="mps", ollama_client=None): self.model_id = model_id self.device = device if torch.backends.mps.is_available() and device == "mps" else "cpu" self.pipe = None self.is_loaded = False self.ollama_client = ollama_client print(f"[LumaForgePipeline] Initialized SD 3.5 Medium pipeline with device: {self.device}") def load_model(self): """Loads SD 3.5 Medium pipeline - latest Stability AI model.""" if self.is_loaded: return True print(f"[LumaForgePipeline] Loading SD 3.5 Medium model onto {self.device}...") print(f"[LumaForgePipeline] Checking local cache at ~/.cache/huggingface/...") try: from diffusers import StableDiffusion3Pipeline import os # Use fp16 for MPS torch_dtype = torch.float16 # Set cache directory explicitly cache_dir = os.path.expanduser("~/.cache/huggingface/hub") print(f"[LumaForgePipeline] Loading SD 3.5 Medium (this will download ~5-6GB on first run)...") self.pipe = StableDiffusion3Pipeline.from_pretrained( self.model_id, text_encoder_3=None, tokenizer_3=None, torch_dtype=torch_dtype, cache_dir=cache_dir, local_files_only=False ) print(f"[LumaForgePipeline] ✅ SD 3.5 Medium loaded successfully") # Memory optimization & Device placement if self.device in ["mps", "cuda"]: try: print(f"[LumaForgePipeline] Enabling model CPU offloading for {self.device} memory optimization...") self.pipe.enable_model_cpu_offload(device=self.device) print(f"[LumaForgePipeline] ✅ Model CPU offloading enabled.") except Exception as e: print(f"[LumaForgePipeline Warning] Failed to enable CPU offloading: {e}. Falling back to full device load.") print(f"[LumaForgePipeline] Moving pipeline to {self.device}...") self.pipe.to(self.device) print(f"[LumaForgePipeline] ✅ Pipeline successfully moved to {self.device}") if self.device == "mps": print(f"[LumaForgePipeline] Enabling attention slicing for MPS memory optimization...") self.pipe.enable_attention_slicing() print(f"[LumaForgePipeline] ✅ Attention slicing enabled.") else: print(f"[LumaForgePipeline] Moving pipeline to {self.device}...") self.pipe.to(self.device) print(f"[LumaForgePipeline] ✅ Pipeline successfully moved to {self.device}") self.is_loaded = True print("[LumaForgePipeline] ✅ SD 3.5 Medium ready for inference!") return True except Exception as e: print(f"[LumaForgePipeline Error] Failed to load SD 3.5 Medium: {e}") print(f"[LumaForgePipeline] Model needs to be downloaded first.") self.is_loaded = False return False def generate(self, prompt: str, aspect_ratio="1:1", steps=20, seed=None, guidance_scale=7.5, negative_prompt="", mock=False) -> dict: """ Generates an image from a prompt. If mock=True or model loading fails, runs in Mock Mode to generate a high-quality stylized abstract visual. """ start_time = time.time() # Determine image dimensions based on aspect ratio width, height = self._get_dimensions(aspect_ratio) # Set random seed if not provided if seed is None or seed == -1: seed = random.randint(0, 9999999) # Get starting memory start_mem_bytes = self._get_mps_memory() image = None used_mock = False gen_prompt = prompt # Extract quoted titles for negative prompt and overlay logic import re titles = re.findall(r'"([^"]+)"', prompt) if not titles: titles = re.findall(r"'([^']+)'", prompt) if mock: print(f"[LumaForgePipeline] Generating mock image (steps={steps}, guidance={guidance_scale})") image = self._generate_mock_image(prompt, width, height, aspect_ratio, seed) used_mock = True # Simulate processing time time.sleep(1.5) else: # SD 3.5 Medium: Use Ollama to optimize prompt for 77-token limit prompt_lower = prompt.lower() # Use Ollama to intelligently compress the prompt if needed if self.ollama_client: print(f"[LumaForgePipeline] Optimizing prompt for SD 3.5 Medium token limit...") optimization = self.ollama_client.optimize_prompt_for_sd35(prompt, max_tokens=256) if optimization["was_compressed"]: print(f"[LumaForgePipeline] ✅ Prompt optimized: {optimization['original_tokens']} → {optimization['final_tokens']} tokens") prompt = optimization["optimized_prompt"] else: print(f"[LumaForgePipeline] ✅ Prompt already optimal ({optimization['original_tokens']} tokens)") else: print(f"[LumaForgePipeline] ⚠️ Ollama not available, using original prompt") # OPTIMIZED NEGATIVE PROMPT (essential negatives only for SD 3.5 Medium) core_negatives = "low quality, blurry" # Add facial negatives for character/portrait images if any(kw in prompt_lower for kw in ["face", "portrait", "character", "person", "wizard", "man", "woman"]): core_negatives = f"{core_negatives}, bad anatomy" # Style-aware exclusions (minimal) if "photorealistic" in prompt_lower or "photo" in prompt_lower: core_negatives = f"{core_negatives}, cartoon" elif "anime" in prompt_lower: core_negatives = f"{core_negatives}, photorealistic" if not negative_prompt: negative_prompt = core_negatives else: negative_prompt = f"{negative_prompt}, {core_negatives}" # If titles found, suppress text generation if titles: negative_prompt = f"{negative_prompt}, text, letters" # Token estimation (rough: ~1.3 chars per token) prompt_tokens = len(prompt) // 1.3 neg_tokens = len(negative_prompt) // 1.3 print(f"[LumaForgePipeline] Token estimate: prompt ~{int(prompt_tokens)}, negative ~{int(neg_tokens)}") if prompt_tokens > 256: print(f"[LumaForgePipeline] ⚠️ Prompt may be truncated (exceeds 256 tokens)") loaded = self.load_model() if not loaded: print("[LumaForgePipeline] Falling back to Mock Generation due to loading failure.") image = self._generate_mock_image(prompt, width, height, aspect_ratio, seed) used_mock = True time.sleep(1.5) else: try: # 8. SD 3.5 OPTIMAL PARAMETERS optimized_steps = 28 optimized_guidance = 4.5 print(f"[LumaForgePipeline] SD 3.5 Medium inference: steps={optimized_steps}, guidance={optimized_guidance}, seed={seed}") print(f"[LumaForgePipeline] Prompt: {prompt[:100]}...") print(f"[LumaForgePipeline] Negative: {negative_prompt[:80]}...") generator = torch.Generator(device=self.device).manual_seed(seed) # Run SD 3.5 Medium diffusion output = self.pipe( prompt=prompt, negative_prompt=negative_prompt, num_inference_steps=optimized_steps, guidance_scale=optimized_guidance, width=width, height=height, generator=generator ) image = output.images[0] print(f"[LumaForgePipeline] ✅ SD 3.5 Medium inference completed") except Exception as e: print(f"[LumaForgePipeline Error] Inference failed: {e}. Falling back to mock image.") image = self._generate_mock_image(prompt, width, height, aspect_ratio, seed) used_mock = True # Apply programmatic typography overlay for actual poster generations if not used_mock and "poster" in prompt.lower() and titles: title = titles[0] print(f"[LumaForgePipeline] Applying programmatic typography overlay for title: '{title}'") image = self._overlay_poster_typography(image, title) latency_sec = time.time() - start_time end_mem_bytes = self._get_mps_memory() # Calculate memory footprint delta or absolute usage memory_used_mb = max(0.0, (end_mem_bytes - start_mem_bytes) / (1024 * 1024)) if memory_used_mb == 0.0 and self.device == "mps": # Show current absolute allocation if delta is 0 memory_used_mb = end_mem_bytes / (1024 * 1024) # Apply LumaForge low-transparency watermark logo overlay image = self._overlay_lumaforge_logo(image) print(f"[LumaForgePipeline] Generation complete: {latency_sec:.2f}s, memory={memory_used_mb:.1f}MB, used_mock={used_mock}") # Construct PNG Metadata metadata = PngInfo() metadata.add_text("prompt", str(gen_prompt)) metadata.add_text("negative_prompt", str(negative_prompt)) metadata.add_text("seed", str(seed)) metadata.add_text("steps", str(steps)) metadata.add_text("guidance_scale", str(guidance_scale)) metadata.add_text("model_id", str(self.model_id)) metadata.add_text("software", "LumaForge AuraGen Core") return { "image": image, "pnginfo": metadata, "latency_sec": latency_sec, "memory_used_mb": memory_used_mb, "seed": seed, "width": width, "height": height, "aspect_ratio": aspect_ratio, "steps": steps, "guidance_scale": guidance_scale, "used_mock": used_mock, "device": self.device } def generate_img2img(self, image: Image.Image, prompt: str, strength=0.5, steps=20, seed=None, guidance_scale=7.5, negative_prompt="", mock=False) -> dict: """ Generates a new image based on an input image and a prompt. If mock=True or model loading fails, runs in Mock Mode to blend the input with a retro-wave composition. """ start_time = time.time() # Set random seed if not provided if seed is None or seed == -1: seed = random.randint(0, 9999999) # Get starting memory start_mem_bytes = self._get_mps_memory() used_mock = False output_image = None is_cartoon = False # Extract quoted titles for negative prompt and overlay logic import re titles = re.findall(r'"([^"]+)"', prompt) if not titles: titles = re.findall(r"'([^']+)'", prompt) # Standardize input image dimensions to match standard generation size (e.g. 512x512) width, height = 512, 512 input_resized = image.convert("RGB").resize((width, height)) if mock: output_image = self._generate_mock_img2img(input_resized, prompt, strength, seed) used_mock = True time.sleep(1.5) else: p_lower = prompt.lower() is_cartoon = any(keyword in p_lower for keyword in ["cartoon", "anime", "ghibli", "sketch", "drawing", "illustration"]) if is_cartoon: # Cap strength to preserve exact facial structure and prevent morphing strength = min(strength, 0.32) # Append high-fidelity style descriptions to prompt if "ghibli" in p_lower: prompt = f"{prompt}, studio ghibli style hand-drawn animation, soft lighting, warm aesthetic, detailed scenery, anime key visual, masterpiece" elif "anime" in p_lower or "cartoon" in p_lower: prompt = f"{prompt}, professional anime key visual, clean lineart, cell shaded colors, vibrant lighting, highly detailed illustration, masterpiece" elif "sketch" in p_lower or "drawing" in p_lower: prompt = f"{prompt}, highly detailed pencil sketch art, hand-drawn pencil shading, clean white paper background, high contrast lines" # Append specialized style-preserving negative prompts to avoid melting/morphing cartoon_neg = "photorealistic, photo, 3d render, morphed faces, deformed eyes, extra limbs, bad anatomy, blurry, low resolution, low quality" if not negative_prompt: negative_prompt = cartoon_neg else: negative_prompt = f"{negative_prompt}, {cartoon_neg}" else: # Quality enhancement trigger words for normal images if "high quality" not in p_lower and "high-resolution" not in p_lower: prompt = f"{prompt}, high-resolution, 8k, detailed, sharp focus" # Quality enhancement negative prompt filter quality_neg = "blurry, blur, out of focus, low quality, low resolution, duplicate, bad anatomy, deformed, distorted" if not negative_prompt: negative_prompt = quality_neg else: negative_prompt = f"{negative_prompt}, {quality_neg}" # If a title is found in the prompt, suppress model text generation to avoid double/garbled lettering if titles: neg_text = "text, letters, words, writing, signage, gibberish lettering, garbled text" negative_prompt = f"{negative_prompt}, {neg_text}" loaded = self.load_model() if not loaded: print("[LumaForgePipeline] Falling back to Mock Img2Img due to loading failure.") output_image = self._generate_mock_img2img(input_resized, prompt, strength, seed) used_mock = True time.sleep(1.5) else: try: from diffusers import StableDiffusionImg2ImgPipeline generator = torch.Generator(device=self.device).manual_seed(seed) # Sharing pipeline components to save memory if hasattr(StableDiffusionImg2ImgPipeline, "from_pipe"): img2img_pipe = StableDiffusionImg2ImgPipeline.from_pipe(self.pipe) else: img2img_pipe = StableDiffusionImg2ImgPipeline( vae=self.pipe.vae, text_encoder=self.pipe.text_encoder, tokenizer=self.pipe.tokenizer, unet=self.pipe.unet, scheduler=self.pipe.scheduler, safety_checker=self.pipe.safety_checker, feature_extractor=self.pipe.feature_extractor ) # Run img2img diffusion output = img2img_pipe( prompt=prompt, image=input_resized, strength=strength, negative_prompt=negative_prompt, num_inference_steps=steps, guidance_scale=guidance_scale, generator=generator ) output_image = output.images[0] except Exception as e: print(f"[LumaForgePipeline Error] Img2Img inference failed: {e}. Falling back to mock blend.") output_image = self._generate_mock_img2img(input_resized, prompt, strength, seed) used_mock = True # Apply programmatic typography overlay for poster modes if not used_mock and "poster" in prompt.lower() and titles: title = titles[0] output_image = self._overlay_poster_typography(output_image, title) # For cartoon/anime styles in real mode, apply pixel-accurate adaptive detail reinforcement and structural blend if not used_mock and is_cartoon and output_image is not None: try: import numpy as np # Convert original and generated images to float arrays orig_arr = np.array(input_resized, dtype=float) gen_arr = np.array(output_image, dtype=float) # 1. High-frequency details transfer (high-pass filter on original grayscale channel) orig_gray = ImageOps.grayscale(input_resized) orig_gray_blurred = orig_gray.filter(ImageFilter.GaussianBlur(radius=1.5)) orig_gray_arr = np.array(orig_gray_blurred, dtype=float) orig_y_arr = np.array(orig_gray, dtype=float) # High-pass values represent sharp face structures and suit web lines high_pass = orig_y_arr - orig_gray_arr high_pass_3d = np.expand_dims(high_pass, axis=2) # Broadcast to 3 channels # Add high-frequency original details to generated output (blending factor 0.30) gen_enhanced_arr = gen_arr + 0.30 * high_pass_3d # 2. Radial Face Protection Mask (centered at face region) # This keeps the face region extremely accurate to the original photo while # allowing the background and shoulders to take on the full cartoon stylization. y_coords, x_coords = np.ogrid[:height, :width] center_y, center_x = int(height * 0.44), int(width * 0.5) # centered at face distance_sq = (y_coords - center_y)**2 + (x_coords - center_x)**2 radius = 110.0 face_mask = np.exp(-distance_sq / (2.0 * (radius**2))) # Face blend: 55% original, 45% generated. Background: 10% original, 90% generated. blend_factor = 0.10 + 0.45 * face_mask blend_factor_3d = np.expand_dims(blend_factor, axis=2) # Pixel-by-pixel composite composited_arr = orig_arr * blend_factor_3d + gen_enhanced_arr * (1.0 - blend_factor_3d) output_image = Image.fromarray(np.clip(composited_arr, 0, 255).astype(np.uint8)) # 3. Dreamy Ghibli Bloom Glow (soft blurred highlight overlay) glow = output_image.filter(ImageFilter.GaussianBlur(6)) output_image = Image.blend(output_image, glow, 0.12) # 4. Stylized color contrast and saturation boost color_enhancer = ImageEnhance.Color(output_image) vibrant = color_enhancer.enhance(1.25) contrast_enhancer = ImageEnhance.Contrast(vibrant) output_image = contrast_enhancer.enhance(1.06) print("[LumaForgePipeline] Successfully executed radial face protection post-processing.") except Exception as e: print(f"[LumaForgePipeline Warning] Adaptive detail restoration failed: {e}") latency_sec = time.time() - start_time end_mem_bytes = self._get_mps_memory() memory_used_mb = max(0.0, (end_mem_bytes - start_mem_bytes) / (1024 * 1024)) if memory_used_mb == 0.0 and self.device == "mps": memory_used_mb = end_mem_bytes / (1024 * 1024) # Apply logo watermark output_image = self._overlay_lumaforge_logo(output_image) # Construct PNG Metadata metadata = PngInfo() metadata.add_text("prompt", str(prompt)) metadata.add_text("negative_prompt", str(negative_prompt)) metadata.add_text("seed", str(seed)) metadata.add_text("steps", str(steps)) metadata.add_text("guidance_scale", str(guidance_scale)) metadata.add_text("strength", str(strength)) metadata.add_text("model_id", str(self.model_id)) metadata.add_text("software", "LumaForge AuraGen Core") return { "image": output_image, "pnginfo": metadata, "latency_sec": latency_sec, "memory_used_mb": memory_used_mb, "seed": seed, "width": width, "height": height, "steps": steps, "guidance_scale": guidance_scale, "strength": strength, "used_mock": used_mock, "device": self.device } def _generate_mock_img2img(self, image: Image.Image, prompt: str, strength: float, seed: int) -> Image.Image: """ Generates a task-aware mock image editing output. Inspects prompt keywords to perform stylized PIL-based transformations (Ghibli paint, snow overlay, color shift, sketch contours, or background replacement) blended according to strength. """ import numpy as np p = prompt.lower() width, height = image.size edited = image.copy() # 1. Style Transfer (Ghibli / anime / painting / watercolor / sketch / cartoon) if any(keyword in p for keyword in ["ghibli", "anime", "painting", "watercolor", "sketch", "cartoon"]): if "sketch" in p: # Pencil sketch effect using highly optimized vectorized NumPy math gray = ImageOps.grayscale(edited) inverted = ImageOps.invert(gray) blurred = inverted.filter(ImageFilter.GaussianBlur(8)) # NumPy vectorized dodge blend gray_arr = np.array(gray, dtype=float) blurred_arr = np.array(blurred, dtype=float) denominator = 255.0 - blurred_arr denominator[denominator == 0] = 1e-5 dodge_arr = (gray_arr * 255.0) / denominator dodge_arr = np.clip(dodge_arr, 0, 255).astype(np.uint8) edited = Image.fromarray(dodge_arr).convert("RGB") # Boost sketch contrast to make outlines pop contrast = ImageEnhance.Contrast(edited) edited = contrast.enhance(1.7) else: # High-fidelity cell-shaded cartoon/anime/Ghibli style img_arr = np.array(edited, dtype=float) # A: Bilateral Filter (edge-preserving texture smoothing) def fast_bilateral_filter(arr, sigma_s=3.0, sigma_r=25.0): h_val, w_val, c_val = arr.shape out = np.zeros_like(arr) w_sum = np.zeros((h_val, w_val, 1)) # 5x5 window for dx in [-2, -1, 0, 1, 2]: for dy in [-2, -1, 0, 1, 2]: if dx == 0 and dy == 0: spatial_w = 1.0 else: spatial_w = np.exp(-(dx**2 + dy**2) / (2.0 * (sigma_s**2))) neighbor = np.roll(arr, shift=(dy, dx), axis=(0, 1)) diff = arr - neighbor color_dist_sq = np.sum(diff**2, axis=2, keepdims=True) color_w = np.exp(-color_dist_sq / (2.0 * (sigma_r**2))) total_w = spatial_w * color_w out += neighbor * total_w w_sum += total_w return out / (w_sum + 1e-5) smoothed_arr = fast_bilateral_filter(img_arr, sigma_s=2.5, sigma_r=20.0) smoothed = Image.fromarray(np.clip(smoothed_arr, 0, 255).astype(np.uint8)) # B: YCbCr Luminance-only cell-shading (preserves skin tones and hues) ycbcr = smoothed.convert("YCbCr") y, cb, cr = ycbcr.split() y_blurred = y.filter(ImageFilter.GaussianBlur(1)) y_arr = np.array(y_blurred, dtype=float) # Quantize Y channel to 5 beautiful stepped levels y_quant = np.zeros_like(y_arr) y_quant[y_arr < 55] = 40 y_quant[(y_arr >= 55) & (y_arr < 110)] = 95 y_quant[(y_arr >= 110) & (y_arr < 165)] = 150 y_quant[(y_arr >= 165) & (y_arr < 220)] = 205 y_quant[y_arr >= 220] = 255 # Smooth transition borders y_new = Image.fromarray(y_quant.astype(np.uint8)).filter(ImageFilter.GaussianBlur(0.8)) shaded = Image.merge("YCbCr", (y_new, cb, cr)).convert("RGB") # C: Grayscale gradient-magnitude organic outline extraction (ink lines) gray = ImageOps.grayscale(smoothed) gray_blurred = gray.filter(ImageFilter.GaussianBlur(1.0)) gray_arr = np.array(gray_blurred, dtype=float) grad_x = np.gradient(gray_arr, axis=1) grad_y = np.gradient(gray_arr, axis=0) grad_mag = np.sqrt(grad_x**2 + grad_y**2) max_grad = grad_mag.max() if max_grad > 0: grad_mag = (grad_mag / max_grad) * 255.0 # Soft threshold for anti-aliasing min_edge, max_edge = 15.0, 35.0 edge_mask = np.zeros_like(grad_mag, dtype=float) mask_range = (grad_mag > min_edge) & (grad_mag < max_edge) edge_mask[mask_range] = (grad_mag[mask_range] - min_edge) / (max_edge - min_edge) edge_mask[grad_mag >= max_edge] = 1.0 # Soften mask to organic ink stroke weight edge_mask_img = Image.fromarray((edge_mask * 255).astype(np.uint8)).filter(ImageFilter.GaussianBlur(0.6)) edge_mask_final = np.array(edge_mask_img, dtype=float) / 255.0 shaded_arr = np.array(shaded, dtype=float) ink_color = np.array([32, 28, 38]) # Dark charcoal ink edge_mask_expanded = np.expand_dims(edge_mask_final, axis=2) final_arr = shaded_arr * (1.0 - edge_mask_expanded) + ink_color * edge_mask_expanded final_cartoon = Image.fromarray(np.clip(final_arr, 0, 255).astype(np.uint8)) # D: Volumetric Bloom / Highlight Glow glow_glow = final_cartoon.filter(ImageFilter.GaussianBlur(12)) final_cartoon = Image.blend(final_cartoon, glow_glow, 0.18) # E: Saturation and Contrast boost color_enhancer = ImageEnhance.Color(final_cartoon) vibrant = color_enhancer.enhance(1.65) contrast_enhancer = ImageEnhance.Contrast(vibrant) edited = contrast_enhancer.enhance(1.15) # F: Ghibli Warm Temp Shift if "ghibli" in p: r, g, b = edited.split() r = r.point(lambda x: min(255, int(x * 1.05))) b = b.point(lambda x: int(x * 0.93)) edited = Image.merge("RGB", (r, g, b)) # 2. Lighting & Weather Changes (snow / winter / rain / night) if "snow" in p or "winter" in p or "rain" in p or "night" in p: if "snow" in p or "winter" in p: r, g, b = edited.split() r = r.point(lambda x: int(x * 0.90)) b = b.point(lambda x: min(255, int(x * 1.10))) edited = Image.merge("RGB", (r, g, b)) elif "rain" in p or "storm" in p: brightness = ImageEnhance.Brightness(edited) edited = brightness.enhance(0.75) r, g, b = edited.split() r = r.point(lambda x: int(x * 0.92)) b = b.point(lambda x: min(255, int(x * 1.06))) edited = Image.merge("RGB", (r, g, b)) elif "night" in p: brightness = ImageEnhance.Brightness(edited) edited = brightness.enhance(0.60) r, g, b = edited.split() r = r.point(lambda x: int(x * 0.85)) g = g.point(lambda x: int(x * 0.85)) b = b.point(lambda x: min(255, int(x * 1.15))) edited = Image.merge("RGB", (r, g, b)) draw = ImageDraw.Draw(edited, "RGBA") import random random.seed(seed) if "snow" in p or "winter" in p: snow_overlay = Image.new("RGBA", (width, height), (0, 0, 0, 0)) snow_draw = ImageDraw.Draw(snow_overlay) for _ in range(90): rx = random.randint(0, width) ry = random.randint(0, height) r_size = random.choice([2, 3, 4, 5]) alpha = random.randint(120, 230) snow_draw.ellipse([rx - r_size, ry - r_size, rx + r_size, ry + r_size], fill=(255, 255, 255, alpha)) snow_overlay = snow_overlay.filter(ImageFilter.GaussianBlur(1.0)) edited = Image.alpha_composite(edited.convert("RGBA"), snow_overlay).convert("RGB") elif "rain" in p: rain_overlay = Image.new("RGBA", (width, height), (0, 0, 0, 0)) rain_draw = ImageDraw.Draw(rain_overlay) for _ in range(150): rx = random.randint(0, width) ry = random.randint(0, height) length = random.randint(10, 22) width_val = random.choice([1, 2]) alpha = random.randint(80, 160) rain_draw.line([(rx, ry), (rx - 3, ry + length)], fill=(210, 230, 255, alpha), width=width_val) rain_overlay = rain_overlay.filter(ImageFilter.GaussianBlur(0.8)) edited = Image.alpha_composite(edited.convert("RGBA"), rain_overlay).convert("RGB") # 3. Object Addition (drone, spaceship, hat, car, etc.) if any(keyword in p for keyword in ["add a", "add some", "insert", "place"]): draw = ImageDraw.Draw(edited, "RGBA") cx, cy = int(width * 0.5), int(height * 0.4) draw.ellipse([cx - 30, cy - 30, cx + 30, cy + 30], outline=(0, 242, 254, 180), width=2) draw.line([(cx - 45, cy), (cx + 45, cy)], fill=(0, 242, 254, 150), width=1) draw.line([(cx, cy - 45), (cx, cy + 45)], fill=(0, 242, 254, 150), width=1) try: font_path = "/System/Library/Fonts/Helvetica.ttc" if not os.path.exists(font_path): font_path = "/System/Library/Fonts/Supplemental/Arial.ttf" font = ImageFont.truetype(font_path, 10) except Exception: font = ImageFont.load_default() draw.text((cx - 15, cy - 6), "ADD+", fill=(255, 255, 255, 240), font=font) # 4. Background Replacement (preserving people and objects perfectly!) if "background" in p or "replace the" in p: # Generate new background mock_bg = self._generate_mock_image(prompt, width, height, "1:1", seed) # Cut out foreground from input image foreground = self.remove_background(image, mock=True) # Paste foreground onto mock background using transparent mask channel mock_bg.paste(foreground, (0, 0), foreground) edited = mock_bg # 5. Color Modification if "color" in p or "recolor" in p or "blue" in p or "red" in p or "green" in p: tint_color = (0, 0, 255) if "red" in p: tint_color = (255, 0, 0) elif "green" in p: tint_color = (0, 255, 0) tint_layer = Image.new("RGB", (width, height), tint_color) edited = Image.blend(edited, tint_layer, 0.15) # Blend the modified image with the original image according to strength return Image.blend(image.convert("RGB"), edited.convert("RGB"), strength) def upscale(self, image: Image.Image, scale_factor: float = 2.0, mock: bool = False) -> dict: """ Upscales the PIL image using high-quality LANCZOS interpolation and applies an unsharp mask to refine edges and details. """ start_time = time.time() start_mem_bytes = self._get_mps_memory() width, height = image.size new_width = int(width * scale_factor) new_height = int(height * scale_factor) # High fidelity resize resampled = image.resize((new_width, new_height), Image.Resampling.LANCZOS) # Edge sharpening filter sharpened = resampled.filter(ImageFilter.UnsharpMask(radius=2, percent=150, threshold=3)) # Apply logo watermark on the final upscaled image to preserve quality final_image = self._overlay_lumaforge_logo(sharpened) latency_sec = time.time() - start_time end_mem_bytes = self._get_mps_memory() memory_used_mb = max(0.0, (end_mem_bytes - start_mem_bytes) / (1024 * 1024)) return { "image": final_image, "latency_sec": latency_sec, "memory_used_mb": memory_used_mb, "width": new_width, "height": new_height, "device": self.device } def remove_background(self, image: Image.Image, mock: bool = False) -> Image.Image: """ Removes the background of the image. If rembg is installed, uses it. Otherwise, runs a high-fidelity chroma-key/color threshold or foreground detection algorithm to create a transparent PNG. """ try: if not mock: import rembg return rembg.remove(image) except ImportError: print("[LumaForgePipeline] rembg not found, falling back to PIL-based color-threshold background removal.") import numpy as np img = image.convert("RGBA") width, height = img.size # Sample corner pixels to find background color corners = [ img.getpixel((0, 0)), img.getpixel((width - 1, 0)), img.getpixel((0, height - 1)), img.getpixel((width - 1, height - 1)) ] from collections import Counter bg_color = Counter(corners).most_common(1)[0][0] bg_r, bg_g, bg_b, _ = bg_color # Convert to numpy array img_arr = np.array(img) rgb = img_arr[:, :, :3].astype(float) alpha = img_arr[:, :, 3].copy() # Distance calculation in numpy bg_rgb = np.array([bg_r, bg_g, bg_b], dtype=float) dists = np.sqrt(np.sum((rgb - bg_rgb) ** 2, axis=2)) threshold = 35.0 # Smooth transition feathering: # below min_thresh: alpha is 0 # above max_thresh: alpha is original alpha # in between: smooth interpolation min_thresh = max(0.0, threshold - 15.0) max_thresh = threshold + 15.0 feathered_alpha = np.zeros_like(dists, dtype=float) # Keep foreground intact feathered_alpha[dists >= max_thresh] = alpha[dists >= max_thresh] # Interpolate transition margins mask = (dists > min_thresh) & (dists < max_thresh) ratio = (dists[mask] - min_thresh) / (max_thresh - min_thresh) feathered_alpha[mask] = alpha[mask] * ratio # Update alpha channel img_arr[:, :, 3] = np.clip(feathered_alpha, 0, 255).astype(np.uint8) img = Image.fromarray(img_arr) # Feather the edges of the alpha channel using a tiny Gaussian blur for a smooth professional cut r, g, b, alpha_channel = img.split() alpha_blurred = alpha_channel.filter(ImageFilter.GaussianBlur(1.0)) return Image.merge("RGBA", (r, g, b, alpha_blurred)) def _get_dimensions(self, aspect_ratio: str) -> tuple: """Returns standard dimensions based on aspect ratio.""" mapping = { "1:1": (512, 512), "16:9": (768, 432), "9:16": (432, 768), "4:3": (640, 480), "3:4": (480, 640) } return mapping.get(aspect_ratio, (512, 512)) def _get_mps_memory(self) -> int: """Returns the current allocated memory on Apple MPS in bytes.""" if self.device == "mps": try: # Returns current memory allocated on MPS return torch.mps.current_allocated_memory() except Exception: return 0 return 0 def _restore_face(self, image: Image.Image) -> Image.Image: """ Restores facial details and clarity using GFPGAN for crystal-clear faces. Falls back gracefully if GFPGAN not available. """ try: from gfpgan import GFPGANer # Initialize GFPGAN restorer = GFPGANer( scale=2, model_path='https://github.com/TencentARC/GFPGAN/releases/download/v1.3.0/GFPGANv1.3.pth', upscale=True, arch='clean', channel_multiplier=2, bg_upsampler=None, device=self.device ) # Convert PIL to numpy (GFPGAN works with numpy arrays) img_np = np.array(image) # Restore faces _, _, output = restorer.enhance(img_np, has_aligned=False, only_center_face=False, pad=10, weight=0.7) # Convert back to PIL restored = Image.fromarray(output) print("[LumaForgePipeline] ✅ Face restoration completed with GFPGAN") return restored except Exception as e: print(f"[LumaForgePipeline Warning] Face restoration failed ({e}). Continuing without restoration.") return image def _upscale_image(self, image: Image.Image, scale: int = 2) -> Image.Image: """ Upscales image using Real-ESRGAN for maximum clarity and detail. Falls back to Lanczos if Real-ESRGAN unavailable. """ try: from basicsr.archs.rrdbnet_arch import RRDBNet from realesrgan import RealESRGANer # Initialize Real-ESRGAN upsampler = RealESRGANer( scale=scale, model_name='RealESRGAN_x2plus', model_path='https://github.com/xinntao/Real-ESRGAN/releases/download/v0.2.1/RealESRGAN_x2plus.pth', tile=400, tile_pad=10, pre_pad=0, half=True if self.device == "mps" else False ) # Convert PIL to numpy img_np = np.array(image) # Upscale output, _ = upsampler.enhance(img_np, outscale=scale) # Convert back to PIL upscaled = Image.fromarray(output) print(f"[LumaForgePipeline] ✅ Image upscaled {scale}x with Real-ESRGAN") return upscaled except Exception as e: print(f"[LumaForgePipeline] Real-ESRGAN unavailable ({e}). Using Lanczos upscaling.") new_size = (image.width * scale, image.height * scale) return image.resize(new_size, Image.Resampling.LANCZOS) def _enhance_clarity(self, image: Image.Image) -> Image.Image: """ Enhances image clarity through multiple post-processing techniques. """ # 1. Unsharp mask for edge enhancement blurred = image.filter(ImageFilter.GaussianBlur(1.0)) img_arr = np.array(image, dtype=float) blur_arr = np.array(blurred, dtype=float) unsharp_mask = img_arr - blur_arr enhanced_arr = img_arr + 0.5 * unsharp_mask enhanced_arr = np.clip(enhanced_arr, 0, 255).astype(np.uint8) enhanced = Image.fromarray(enhanced_arr) # 2. Contrast boost contrast_enhancer = ImageEnhance.Contrast(enhanced) enhanced = contrast_enhancer.enhance(1.1) # 3. Sharpness boost sharpness_enhancer = ImageEnhance.Sharpness(enhanced) enhanced = sharpness_enhancer.enhance(1.2) print("[LumaForgePipeline] ✅ Clarity enhancement applied") return enhanced def _generate_mock_image(self, prompt: str, width: int, height: int, aspect_ratio: str, seed: int) -> Image: """ Generates a beautiful, highly stylized mock image dynamically matching the prompt. Draws a detailed cyberpunk retro-wave sci-fi landscape (stars, glowing sun, perspective grid, silhouette mountains) instead of a plain gradient, providing a stunning visual output in mock mode. """ import math random.seed(seed) # 1. Base gradient colors based on prompt content colors = self._determine_colors_from_prompt(prompt) c1, c2 = colors[0], colors[1] # Create base canvas base = Image.new("RGB", (width, height), c1) draw = ImageDraw.Draw(base) # Draw vertical background gradient for y in range(height): ratio = y / height r = int(c1[0] * (1 - ratio) + c2[0] * ratio) g = int(c1[1] * (1 - ratio) + c2[1] * ratio) b = int(c1[2] * (1 - ratio) + c2[2] * ratio) draw.line([(0, y), (width, y)], fill=(r, g, b)) # Draw starfield in the sky (top half of image) num_stars = random.randint(30, 60) for _ in range(num_stars): sx = random.randint(0, width) sy = random.randint(0, int(height * 0.6)) star_size = random.choice([1, 2, 3]) star_alpha = random.randint(100, 255) if star_size > 1: draw.line([(sx - star_size, sy), (sx + star_size, sy)], fill=(255, 255, 255, star_alpha)) draw.line([(sx, sy - star_size), (sx, sy + star_size)], fill=(255, 255, 255, star_alpha)) else: draw.point((sx, sy), fill=(255, 255, 255, star_alpha)) # 2. Draw a glowing retro sun/planet sun_r = int(min(width, height) * 0.22) sun_cx = random.randint(int(width * 0.3), int(width * 0.7)) sun_cy = int(height * 0.45) sun_color = c2 if len(colors) > 2: sun_color = colors[2] else: sun_color = (255, 110, 0) if "fire" in prompt.lower() else (255, 0, 127) for r_step in range(sun_r, 0, -2): glow_alpha = int(80 * (1 - r_step / sun_r)) draw.ellipse( [sun_cx - r_step, sun_cy - r_step, sun_cx + r_step, sun_cy + r_step], outline=(sun_color[0], sun_color[1], sun_color[2], glow_alpha), width=2 ) slice_height = 6 gap_height = 3 for y_offset in range(-sun_r, sun_r): x_half = int(math.sqrt(max(0, sun_r**2 - y_offset**2))) current_y = sun_cy + y_offset # Retro scanline gaps at the bottom of the sun if y_offset > 0 and (y_offset // (slice_height + gap_height)) % 2 == 0: continue draw.line([(sun_cx - x_half, current_y), (sun_cx + x_half, current_y)], fill=sun_color) # 3. Draw Cyberpunk Perspective Grid (Ground) horizon_y = int(height * 0.55) grid_color = (0, 242, 254) if "neon" in prompt.lower() or "cyberpunk" in prompt.lower() else (255, 255, 255) # Horizontal lines getting closer as they approach the horizon num_grid_lines = 12 for i in range(num_grid_lines): t = i / (num_grid_lines - 1) line_y = int(horizon_y + (height - horizon_y) * (t ** 2.2)) alpha = int(40 + 180 * t) draw.line([(0, line_y), (width, line_y)], fill=(grid_color[0], grid_color[1], grid_color[2], alpha), width=1) # Vanishing lines num_vanishing_lines = 16 vanishing_cx = width // 2 for i in range(num_vanishing_lines): t = i / (num_vanishing_lines - 1) start_x = int(width * (t * 2 - 0.5)) alpha = int(120) draw.line([(vanishing_cx, horizon_y), (start_x, height)], fill=(grid_color[0], grid_color[1], grid_color[2], alpha), width=1) # 4. Draw Silhouette mountains at the horizon num_mountains = 3 for idx in range(num_mountains): m_points = [] m_width = random.randint(int(width * 0.4), int(width * 0.8)) m_height = random.randint(40, 90) m_cx = random.randint(0, width) m_points.append((m_cx - m_width // 2, horizon_y)) m_points.append((m_cx - m_width // 4, horizon_y - m_height // 2)) m_points.append((m_cx, horizon_y - m_height)) m_points.append((m_cx + m_width // 4, horizon_y - m_height // 3)) m_points.append((m_cx + m_width // 2, horizon_y)) draw.polygon(m_points, fill=(10, 10, 25)) # Composite base with overlays image = base.convert("RGB") # 5. Enhance detail and sharpness across the entire mock canvas (no edge blurs) image = image.filter(ImageFilter.SHARPEN) # 6. Add minimalist border frame draw_frame = ImageDraw.Draw(image, "RGBA") draw_frame.rectangle([15, 15, width - 15, height - 15], outline=(255, 255, 255, 60), width=2) # Metadata printouts settings_text = f"SEED: {seed} | RES: {width}x{height} | {aspect_ratio} | MOCK ENGINE" draw_frame.text((30, height - 48), settings_text, fill=(255, 255, 255, 140)) title_limit = prompt[:38] + ("..." if len(prompt) > 38 else "") draw_frame.text((30, height - 68), f"PROMPT: {title_limit.upper()}", fill=(255, 255, 255, 220)) return image def _determine_colors_from_prompt(self, prompt: str) -> list: """Determines color palette based on keywords in the prompt.""" p = prompt.lower() palettes = { "cyberpunk": [(26, 8, 46), (255, 0, 127)], # Dark purple -> hot pink "neon": [(10, 25, 47), (0, 242, 254)], # Deep blue -> bright cyan "fire": [(40, 10, 5), (255, 110, 0)], # Dark red -> intense orange "forest": [(10, 30, 20), (46, 204, 113)], # Deep green -> emerald "cosmic": [(11, 11, 28), (142, 68, 173)], # Starry indigo -> amethyst "sunset": [(230, 81, 0), (253, 216, 53)], # Deep orange -> amber gold "character": [(20, 20, 20), (140, 140, 150)], # Studio grey -> soft silver "poster": [(10, 15, 25), (241, 196, 15)] # Dark blue-gray -> poster gold } for key, palette in palettes.items(): if key in p: return palette # Default harmonized cool blue gradient return [(15, 32, 67), (70, 130, 180)] def _overlay_poster_typography(self, image: Image, title: str) -> Image: """Overlays professional premium typography on the generated movie poster image.""" try: from PIL import ImageDraw, ImageFont, ImageFilter, ImageOps import os import re # Copy base canvas img = image.copy() width, height = img.size # Clean title title_text = title.strip().upper() # Detect layout style from prompt/title text style_type = "cinematic" if any(w in title_text.lower() for w in ["cyber", "neon", "retro", "hack", "system", "matrix", "future", "laser", "star", "cosmic", "galaxy"]): style_type = "scifi" elif any(w in title_text.lower() for w in ["luxury", "gold", "royal", "silent", "whisper", "minimal", "white", "glass", "vogue", "velvet"]): style_type = "luxury" # Helper for character-spaced drawing def get_spaced_text_width(text, font, spacing=6): w = 0 for char in text: bbox = font.getbbox(char) char_w = bbox[2] - bbox[0] w += char_w + spacing return w - spacing if w > 0 else 0 def draw_spaced_text(draw, position, text, font, fill, spacing=6, shadow_fill=None, shadow_offset=(1, 1)): x, y = position ox, oy = shadow_offset for char in text: if shadow_fill: draw.text((x + ox, y + oy), char, fill=shadow_fill, font=font) draw.text((x, y), char, fill=fill, font=font) bbox = font.getbbox(char) char_w = bbox[2] - bbox[0] x += char_w + spacing def draw_gradient_text(target_img, position, text, font, spacing, top_color, bottom_color, shadow_fill=None, shadow_offset=(2, 2)): """Draws text with a beautiful top-to-bottom vertical color gradient.""" w = get_spaced_text_width(text, font, spacing) bbox = font.getbbox("A") h = bbox[3] - bbox[1] + 15 # Create a mask for the text mask = Image.new("L", (w + 40, h + 20), 0) mask_draw = ImageDraw.Draw(mask) # Draw spaced text on mask x_m, y_m = 20, 10 for char in text: mask_draw.text((x_m, y_m), char, fill=255, font=font) c_bbox = font.getbbox(char) char_w = c_bbox[2] - c_bbox[0] x_m += char_w + spacing # Create gradient image of the same size gradient = Image.new("RGBA", (w + 40, h + 20)) g_draw = ImageDraw.Draw(gradient) for y in range(h + 20): ratio = y / (h + 20) r = int(top_color[0] + (bottom_color[0] - top_color[0]) * ratio) g = int(top_color[1] + (bottom_color[1] - top_color[1]) * ratio) b = int(top_color[2] + (bottom_color[2] - top_color[2]) * ratio) g_draw.line([(0, y), (w + 40, y)], fill=(r, g, b, 255)) # Apply mask to gradient text_img = Image.new("RGBA", (w + 40, h + 20)) text_img.paste(gradient, (0, 0), mask) # Draw shadow on the main image if requested if shadow_fill: sx, sy = position[0] + shadow_offset[0], position[1] + shadow_offset[1] shadow_img = Image.new("RGBA", (w + 40, h + 20), (shadow_fill[0], shadow_fill[1], shadow_fill[2], shadow_fill[3])) target_img.paste(shadow_img, (sx - 20, sy - 10), mask) # Paste onto main image target_img.paste(text_img, (position[0] - 20, position[1] - 10), mask) # Setup fonts based on theme font_paths = { "scifi": "/System/Library/Fonts/Supplemental/Futura.ttc", "luxury": "/System/Library/Fonts/Supplemental/Didot.ttc", "cinematic": "/System/Library/Fonts/Supplemental/Copperplate.ttc" } sub_font_paths = { "scifi": "/System/Library/Fonts/Supplemental/Futura.ttc", "luxury": "/System/Library/Fonts/Supplemental/Baskerville.ttc", "cinematic": "/System/Library/Fonts/Supplemental/Georgia.ttf" } # Select active fonts with Helvetica fallbacks font_path = font_paths.get(style_type, "/System/Library/Fonts/Helvetica.ttc") sub_font_path = sub_font_paths.get(style_type, "/System/Library/Fonts/Helvetica.ttc") if not os.path.exists(font_path): font_path = "/System/Library/Fonts/Helvetica.ttc" if not os.path.exists(sub_font_path): sub_font_path = "/System/Library/Fonts/Helvetica.ttc" # Font size heuristics title_font_size = max(26, int(height * 0.08)) sub_font_size = max(10, int(height * 0.024)) credits_font_size = max(8, int(height * 0.016)) # Determine maximum allowable width max_w = int(width * 0.88) try: t_font = ImageFont.truetype(font_path, title_font_size) # Compute width with spacing (default spacing is 8 for title) t_spacing = 8 if style_type != "luxury" else 14 t_w = get_spaced_text_width(title_text, t_font, spacing=t_spacing) # Shrink title if too wide while t_w > max_w and title_font_size > 16: title_font_size -= 2 t_font = ImageFont.truetype(font_path, title_font_size) t_w = get_spaced_text_width(title_text, t_font, spacing=t_spacing) except Exception: t_font = ImageFont.load_default() t_spacing = 4 t_w = len(title_text) * (8 + t_spacing) # Create overlay canvas overlay = Image.new("RGBA", (width, height), (0, 0, 0, 0)) if style_type == "scifi": # 1. Cyberpunk/Sci-Fi Theme # Bottom vignette (cyan/dark) for y in range(int(height * 0.6), height): ratio = (y - int(height * 0.6)) / (height * 0.4) alpha = int(210 * (ratio ** 1.5)) draw_line = ImageDraw.Draw(overlay) draw_line.line([(0, y), (width, y)], fill=(5, 10, 20, alpha)) # Draw Title at the bottom with gradient tx = (width - t_w) // 2 ty = int(height * 0.82) draw_gradient_text( overlay, (tx, ty), title_text, t_font, spacing=t_spacing, top_color=(0, 255, 255), bottom_color=(0, 128, 255), shadow_fill=(255, 0, 128, 200), shadow_offset=(-2, 2) ) # Tagline / Subtitle draw_overlay = ImageDraw.Draw(overlay) sub_text = "A U R A _ G E N // N E T _ S Y S _ A C T I V E" try: s_font = ImageFont.truetype(sub_font_path, sub_font_size) s_w = get_spaced_text_width(sub_text, s_font, spacing=3) except Exception: s_font = ImageFont.load_default() s_w = len(sub_text) * 10 sx = (width - s_w) // 2 sy = int(height * 0.76) draw_spaced_text(draw_overlay, (sx, sy), sub_text, s_font, fill=(0, 240, 255, 220), spacing=3, shadow_fill=(0, 0, 0, 180)) # Top coordinates HUD hud_text = "COORD: 35.6762° N, 139.6503° E | SYS: ONLINE" try: h_font = ImageFont.truetype(sub_font_path, int(credits_font_size * 0.9)) except Exception: h_font = ImageFont.load_default() draw_overlay.text((30, 30), hud_text, fill=(0, 255, 255, 120), font=h_font) elif style_type == "luxury": # 2. Minimalist Luxury Theme # Top vignette (subtle dark vignette at top) for y in range(0, int(height * 0.35)): ratio = 1.0 - (y / (height * 0.35)) alpha = int(140 * (ratio ** 1.8)) draw_line = ImageDraw.Draw(overlay) draw_line.line([(0, y), (width, y)], fill=(8, 8, 12, alpha)) # Title at the top center with pearl gradient tx = (width - t_w) // 2 ty = int(height * 0.15) draw_gradient_text( overlay, (tx, ty), title_text, t_font, spacing=t_spacing, top_color=(255, 255, 255), bottom_color=(235, 235, 240), shadow_fill=(0, 0, 0, 180), shadow_offset=(2, 2) ) # Gold separator line under title draw_overlay = ImageDraw.Draw(overlay) line_y = ty + int(height * 0.09) line_w = int(t_w * 0.6) lx1 = (width - line_w) // 2 lx2 = lx1 + line_w draw_overlay.line([(lx1, line_y), (lx2, line_y)], fill=(212, 175, 55, 180), width=1) # gold line # Elegant tagline sub_text = "L U M A F O R G E P R E S E N T S" try: s_font = ImageFont.truetype(sub_font_path, int(sub_font_size * 0.95)) # Make it italic if Baskerville if "Baskerville" in sub_font_path: s_font = ImageFont.truetype("/System/Library/Fonts/Supplemental/Baskerville.ttc", int(sub_font_size * 0.95), index=1) s_w = get_spaced_text_width(sub_text, s_font, spacing=4) except Exception: s_font = ImageFont.load_default() s_w = len(sub_text) * 10 sx = (width - s_w) // 2 sy = ty - int(height * 0.05) draw_spaced_text(draw_overlay, (sx, sy), sub_text, s_font, fill=(212, 175, 55, 220), spacing=4, shadow_fill=(0, 0, 0, 160), shadow_offset=(1, 1)) else: # 3. Cinematic Action Theme (Default) # Bottom vignette (dark rich vignette) for y in range(int(height * 0.52), height): ratio = (y - int(height * 0.52)) / (height * 0.48) alpha = int(230 * (ratio ** 2.0)) draw_line = ImageDraw.Draw(overlay) draw_line.line([(0, y), (width, y)], fill=(4, 4, 6, alpha)) # Title at bottom with warm silver/gold metallic gradient tx = (width - t_w) // 2 ty = int(height * 0.80) draw_gradient_text( overlay, (tx, ty), title_text, t_font, spacing=t_spacing, top_color=(255, 255, 255), bottom_color=(220, 215, 200), shadow_fill=(0, 0, 0, 245), shadow_offset=(3, 3) ) # Dynamic billing block text (credits line) draw_overlay = ImageDraw.Draw(overlay) credits_line = "STARRING GENERATIVE IMAGINATION • EXECUTIVE PRODUCERS LUMAFORGE LABS • MUSIC BY NEURAL SYNTH" try: c_font = ImageFont.truetype(font_path, credits_font_size) c_w = get_spaced_text_width(credits_line, c_font, spacing=2) # Shrink if too wide while c_w > max_w and credits_font_size > 6: credits_font_size -= 1 c_font = ImageFont.truetype(font_path, credits_font_size) c_w = get_spaced_text_width(credits_line, c_font, spacing=2) except Exception: c_font = ImageFont.load_default() c_w = len(credits_line) * 8 cx_pos = (width - c_w) // 2 cy_pos = int(height * 0.90) draw_spaced_text(draw_overlay, (cx_pos, cy_pos), credits_line, c_font, fill=(160, 160, 160, 200), spacing=2) # Tagline above title tagline = "THE FUTURE OF CREATIVE ARTISTRY" try: s_font = ImageFont.truetype(sub_font_path, sub_font_size) # Make it italic if Georgia if "Georgia" in sub_font_path: s_font = ImageFont.truetype("/System/Library/Fonts/Supplemental/Georgia Italic.ttf", sub_font_size) s_w = get_spaced_text_width(tagline, s_font, spacing=3) except Exception: s_font = ImageFont.load_default() s_w = len(tagline) * 10 sx = (width - s_w) // 2 sy = ty - int(height * 0.06) draw_spaced_text(draw_overlay, (sx, sy), tagline, s_font, fill=(225, 225, 225, 255), spacing=3, shadow_fill=(0, 0, 0, 200)) # Small minimalist line line_y = (ty + sy + int(height * 0.02)) // 2 line_w = int(width * 0.35) lx1 = (width - line_w) // 2 lx2 = lx1 + line_w draw_overlay.line([(lx1, line_y), (lx2, line_y)], fill=(255, 255, 255, 70), width=1) # Convert base image to RGBA, composite overlay, convert back to RGB img_rgba = img.convert("RGBA") composited = Image.alpha_composite(img_rgba, overlay) return composited.convert("RGB") except Exception as e: print(f"[LumaForgePipeline Warning] Failed to overlay premium typography: {e}") return image def _overlay_lumaforge_logo(self, image: Image) -> Image: """ Overlays a beautiful, premium, glassmorphic LumaForge brand logo badge in the bottom-right corner of the image to make it stand out extremely clearly. """ try: from PIL import ImageDraw, ImageFont img = image.copy() # Create a separate RGBA draw layer for glassmorphism transparency blending overlay = Image.new("RGBA", img.size, (0, 0, 0, 0)) draw = ImageDraw.Draw(overlay) width, height = img.size # Badge dimensions & positioning (Bottom-Right corner) badge_w = 165 badge_h = 32 padding = 20 x1 = width - padding - badge_w y1 = height - padding - badge_h x2 = width - padding y2 = height - padding # 1. Draw Glassmorphic Badge Plate (with fallback if rounded_rectangle fails) badge_fill = (10, 10, 16, 190) # Sleek dark translucent plate badge_border = (255, 255, 255, 45) # Subtle white glass border try: draw.rounded_rectangle([x1, y1, x2, y2], radius=8, fill=badge_fill, outline=badge_border, width=1) except AttributeError: # Fallback to standard rectangle if Pillow version is old draw.rectangle([x1, y1, x2, y2], fill=badge_fill, outline=badge_border, width=1) # Load system font for crisp, premium text rendering font_path = "/System/Library/Fonts/Helvetica.ttc" if not os.path.exists(font_path): font_path = "/System/Library/Fonts/Supplemental/Arial.ttf" try: font = ImageFont.truetype(font_path, 10) except Exception: font = ImageFont.load_default() # Minimalist spaced logo text text = "L U M A F O R G E" # 2. Draw brand text inside the badge text_x = x1 + 14 # Center the text vertically text_y = y1 + (badge_h // 2) - 6 # Draw subtle text drop shadow draw.text((text_x + 1, text_y + 1), text, fill=(0, 0, 0, 180), font=font) draw.text((text_x, text_y), text, fill=(255, 255, 255, 240), font=font) # 3. Draw geometric diamond forge logo next to it diamond_w = 12 diamond_h = 12 center_x = x2 - 20 center_y = y1 + (badge_h // 2) diamond_points = [ (center_x, center_y - diamond_h//2), (center_x + diamond_w//2, center_y), (center_x, center_y + diamond_h//2), (center_x - diamond_w//2, center_y) ] # Diamond shadow shadow_points = [(x + 1, y + 1) for x, y in diamond_points] draw.polygon(shadow_points, fill=(0, 0, 0, 180)) draw.polygon(diamond_points, fill=(255, 255, 255, 240)) # Inner dark core accent cut inner_w = 4 inner_h = 4 inner_points = [ (center_x, center_y - inner_h//2), (center_x + inner_w//2, center_y), (center_x, center_y + inner_h//2), (center_x - inner_w//2, center_y) ] draw.polygon(inner_points, fill=(10, 10, 16, 255)) # Alpha composite the overlay layer onto the original image final_img = Image.alpha_composite(img.convert("RGBA"), overlay) return final_img.convert("RGB") except Exception as e: print(f"[LumaForgePipeline Warning] Failed to overlay watermark logo badge: {e}") return image def colorize(self, image: Image.Image, style: str = "vibrant", mock: bool = False) -> dict: """ Colorizes an image with different color grading styles. Styles: vibrant, warm, cool, vintage, sepia """ start_time = time.time() start_mem_bytes = self._get_mps_memory() result = image.convert("RGB").copy() if style == "vibrant": # Boost saturation and contrast enhancer = ImageEnhance.Color(result) result = enhancer.enhance(1.6) contrast = ImageEnhance.Contrast(result) result = contrast.enhance(1.2) elif style == "warm": # Warm color temperature shift r, g, b = result.split() r = r.point(lambda x: min(255, int(x * 1.15))) g = g.point(lambda x: int(x * 0.95)) result = Image.merge("RGB", (r, g, b)) elif style == "cool": # Cool color temperature shift r, g, b = result.split() r = r.point(lambda x: int(x * 0.85)) b = b.point(lambda x: min(255, int(x * 1.15))) result = Image.merge("RGB", (r, g, b)) elif style == "vintage": # Vintage film look enhancer = ImageEnhance.Color(result) result = enhancer.enhance(0.8) result = result.convert("RGBA") overlay = Image.new("RGBA", result.size, (255, 200, 100, 30)) result = Image.alpha_composite(result, overlay).convert("RGB") elif style == "sepia": # Classic sepia tone result = result.convert("LA").convert("RGB") r, g, b = result.split() r = r.point(lambda x: min(255, int(x * 1.2))) g = g.point(lambda x: int(x * 0.95)) b = b.point(lambda x: int(x * 0.7)) result = Image.merge("RGB", (r, g, b)) latency_sec = time.time() - start_time end_mem_bytes = self._get_mps_memory() memory_used_mb = max(0.0, (end_mem_bytes - start_mem_bytes) / (1024 * 1024)) return { "image": result, "latency_sec": latency_sec, "memory_used_mb": memory_used_mb, "style": style } def restore_face(self, image: Image.Image, intensity: str = "medium", mock: bool = False) -> dict: """ Restores and enhances facial features. Intensity levels: low, medium, high, ultra """ start_time = time.time() start_mem_bytes = self._get_mps_memory() result = image.convert("RGB").copy() # Intensity mapping for enhancement factors intensity_map = { "low": 1.1, "medium": 1.3, "high": 1.5, "ultra": 1.8 } factor = intensity_map.get(intensity, 1.3) # Step 1: Denoise result = result.filter(ImageFilter.MedianFilter(size=3)) # Step 2: Sharpen sharpness = ImageEnhance.Sharpness(result) result = sharpness.enhance(factor) # Step 3: Enhance contrast contrast = ImageEnhance.Contrast(result) result = contrast.enhance(factor * 0.7) # Step 4: Boost color vibrancy for skin tones color = ImageEnhance.Color(result) result = color.enhance(1.15) latency_sec = time.time() - start_time end_mem_bytes = self._get_mps_memory() memory_used_mb = max(0.0, (end_mem_bytes - start_mem_bytes) / (1024 * 1024)) return { "image": result, "latency_sec": latency_sec, "memory_used_mb": memory_used_mb, "intensity": intensity }