Text-to-Image
Diffusers
English
sdxl
sdxl-turbo
stable-diffusion
image-to-image
image-generation
image-editing
fastapi
mps
Instructions to use sujithputta/Lumaforge with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Diffusers
How to use sujithputta/Lumaforge with Diffusers:
pip install -U diffusers transformers accelerate
import torch from diffusers import DiffusionPipeline # switch to "mps" for apple devices pipe = DiffusionPipeline.from_pretrained("sujithputta/Lumaforge", dtype=torch.bfloat16, device_map="cuda") prompt = "Astronaut in a jungle, cold color palette, muted colors, detailed, 8k" image = pipe(prompt).images[0] - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- Draw Things
- DiffusionBee
Deploy architectural upgrades: VRAM CPU offloading, sequential task queue, pipeline registry, and SQLite database manager
9748021 | 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 | |
| } | |