# app.py — MangaMorph (Gradio) — backwards-compatible, CPU-friendly import os import random import time import numpy as np from PIL import Image, ImageOps import gradio as gr import torch from diffusers import DiffusionPipeline, EulerDiscreteScheduler # ---------- CONFIG ---------- MODEL_ID = os.getenv("MODEL_ID", "hakurei/waifu-diffusion") # change if needed HF_TOKEN = os.getenv("HUGGINGFACE_HUB_TOKEN", None) device = "cuda" if torch.cuda.is_available() else "cpu" torch_dtype = torch.float16 if device == "cuda" else torch.float32 # CPU-friendly defaults & limits DEFAULT_WIDTH = 384 DEFAULT_HEIGHT = 384 DEFAULT_STEPS = 10 DEFAULT_GUIDANCE = 5.5 MAX_SEED = np.iinfo(np.int32).max # ---------- Load pipeline (lazy) ---------- PIPE = None def load_pipeline(): global PIPE if PIPE is not None: return PIPE try: pipe = DiffusionPipeline.from_pretrained( MODEL_ID, torch_dtype=torch_dtype, use_auth_token=HF_TOKEN, ) # Try to set EulerDiscreteScheduler if provided by model repo try: scheduler = EulerDiscreteScheduler.from_pretrained(MODEL_ID, subfolder="scheduler") pipe.scheduler = scheduler except Exception: pass pipe = pipe.to(device) # Optional: disable safety checker on CPU for speed (non-ideal but common) try: pipe.safety_checker = None except Exception: pass PIPE = pipe return PIPE except Exception as e: raise RuntimeError(f"Model load failed: {e}") # ---------- Helpers ---------- DEFAULT_NEG = ( "low quality, bad anatomy, blurry, extra limbs, malformed, deformed, " "watermark, text, signature, lowres" ) def tidy_image(img: Image.Image, max_side=1024): img = img.convert("RGB") if max(img.size) > max_side: img = ImageOps.contain(img, (max_side, max_side)) return img # ---------- Inference ---------- def infer( prompt: str, negative_prompt: str, seed: int, randomize_seed: bool, width: int, height: int, guidance_scale: float, num_inference_steps: int, ): start = time.time() if not prompt or prompt.strip() == "": return None, "Enter a prompt." if randomize_seed or int(seed) == 0: seed = random.randint(0, MAX_SEED) else: seed = int(seed) % MAX_SEED try: pipe = load_pipeline() except Exception as e: return None, f"Model load error: {e}" # enforce CPU-friendly caps width = int(min(max(256, width), 512)) height = int(min(max(256, height), 512)) steps = int(min(max(4, num_inference_steps), 20)) gen = torch.Generator(device=device).manual_seed(seed) try: out = pipe( prompt=prompt, negative_prompt=(negative_prompt or DEFAULT_NEG), width=width, height=height, guidance_scale=float(guidance_scale), num_inference_steps=steps, generator=gen, ) image = tidy_image(out.images[0], max_side=1024) elapsed = time.time() - start return image, f"Done — Seed: {seed} • {int(elapsed)}s" except Exception: # lighter retry try: out = pipe( prompt=prompt, negative_prompt=(negative_prompt or DEFAULT_NEG), width=width, height=height, guidance_scale=max(3.0, float(guidance_scale) - 1.0), num_inference_steps=max(4, steps - 4), generator=gen, ) image = tidy_image(out.images[0], max_side=1024) elapsed = time.time() - start return image, f"Recovered (retry) — Seed: {seed} • {int(elapsed)}s" except Exception as e2: return None, f"Generation failed: {e2}" # ---------- UI (compatible with older/newer Gradio) ---------- css = """ /* Vibrant purple-pink gradient background */ body, .gradio-container { background: linear-gradient(135deg, #d946ef 0%, #a855f7 25%, #8b5cf6 50%, #7c3aed 75%, #6366f1 100%) !important; font-family: "Inter", system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial; color: #ffffff !important; min-height: 100vh; } /* Main container styling */ .contain, .gradio-container > div { background: transparent !important; } /* Header card - bright gradient with glow */ .header { padding: 20px 24px; border-radius: 16px; background: linear-gradient(135deg, #ec4899 0%, #f97316 50%, #facc15 100%); color: white; box-shadow: 0 8px 32px rgba(236, 72, 153, 0.4), 0 0 60px rgba(249, 115, 22, 0.3); margin-bottom: 20px; } /* Brand/title */ .brand { font-weight: 900; font-size: 28px; letter-spacing: 0.5px; color: #fff; text-shadow: 2px 2px 8px rgba(0,0,0,0.3); } /* Subtitle under brand */ .small { font-size: 14px; color: rgba(255,255,255,0.95); margin-top: 8px; font-weight: 500; } /* All blocks and containers - vibrant semi-transparent cards */ .gr-block, .gr-box, .gr-form, .gr-panel { background: rgba(255, 255, 255, 0.15) !important; backdrop-filter: blur(10px) !important; border-radius: 16px !important; border: 2px solid rgba(255, 255, 255, 0.25) !important; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1) !important; padding: 16px !important; } /* Input fields - bright with good contrast */ .gr-textbox, .gr-input, textarea, input { background: rgba(255, 255, 255, 0.95) !important; color: #1f2937 !important; border: 2px solid rgba(236, 72, 153, 0.3) !important; border-radius: 12px !important; padding: 12px !important; font-size: 15px !important; font-weight: 500 !important; } .gr-textbox::placeholder, textarea::placeholder, input::placeholder { color: rgba(31, 41, 55, 0.5) !important; } /* Labels - bright and visible */ label, .gr-label, .gr-box label { color: #ffffff !important; font-weight: 700 !important; font-size: 15px !important; text-shadow: 1px 1px 3px rgba(0,0,0,0.3) !important; margin-bottom: 8px !important; } /* Buttons - super vibrant gradient */ .gr-button, button { background: linear-gradient(135deg, #ff0080 0%, #ff8c00 50%, #ffd700 100%) !important; color: white !important; font-weight: 800 !important; border: none !important; box-shadow: 0 6px 24px rgba(255, 0, 128, 0.4), 0 0 40px rgba(255, 140, 0, 0.3) !important; border-radius: 12px !important; padding: 14px 24px !important; font-size: 16px !important; text-transform: uppercase; letter-spacing: 0.5px; transition: all 0.3s ease !important; } .gr-button:hover, button:hover { transform: translateY(-2px); box-shadow: 0 8px 32px rgba(255, 0, 128, 0.6), 0 0 60px rgba(255, 140, 0, 0.5) !important; } /* Sliders - bright colors */ .gr-slider input[type="range"] { background: rgba(255, 255, 255, 0.2) !important; } .gr-slider input[type="range"]::-webkit-slider-thumb { background: linear-gradient(135deg, #ff0080, #ff8c00) !important; border: 3px solid white !important; box-shadow: 0 2px 8px rgba(0,0,0,0.3) !important; } /* Accordion - vibrant */ .gr-accordion { background: rgba(255, 255, 255, 0.1) !important; border: 2px solid rgba(255, 255, 255, 0.2) !important; border-radius: 12px !important; } .gr-accordion summary { color: #ffffff !important; font-weight: 700 !important; background: rgba(236, 72, 153, 0.3) !important; padding: 12px !important; border-radius: 10px !important; } /* Image containers - bright white background */ .gr-image, .gr-gallery { background: rgba(255, 255, 255, 0.95) !important; border-radius: 12px !important; padding: 12px !important; border: 2px solid rgba(236, 72, 153, 0.3) !important; } /* Status textbox - bright and visible */ .gr-textbox[aria-label="Status"] { background: rgba(255, 255, 255, 0.9) !important; color: #1f2937 !important; border: 2px solid rgba(16, 185, 129, 0.5) !important; font-weight: 600 !important; } /* Examples - vibrant cards */ .gr-examples { background: rgba(255, 255, 255, 0.1) !important; border-radius: 12px !important; padding: 12px !important; } .gr-examples .gr-button { background: rgba(139, 92, 246, 0.8) !important; font-size: 13px !important; padding: 10px 16px !important; } /* Checkbox styling */ .gr-checkbox { color: #ffffff !important; } .gr-checkbox input[type="checkbox"] { border: 2px solid rgba(255, 255, 255, 0.5) !important; background: rgba(255, 255, 255, 0.2) !important; } /* Number inputs */ .gr-number input { background: rgba(255, 255, 255, 0.95) !important; color: #1f2937 !important; } /* Markdown text */ .gr-markdown, .markdown { color: #ffffff !important; } .gr-markdown strong { color: #fbbf24 !important; font-weight: 800 !important; } /* Mobile-friendly adjustments */ @media (max-width: 720px) { .header { text-align: center; } .brand { font-size: 24px; } .gr-button, button { font-size: 14px !important; padding: 12px 20px !important; } } """ examples = [ "anime girl standing on a cherry-blossom bridge at sunset, cinematic lighting, detailed eyes", "young samurai on a misty mountain path, dramatic clouds, anime style", "cozy studio apartment with anime character reading by window, warm lighting", ] with gr.Blocks(css=css, title="MangaMorph — Anime Scene Generator") as demo: with gr.Row(): with gr.Column(scale=2): gr.Markdown( "