Spaces:
Runtime error
Runtime error
| import gradio as gr | |
| from PIL import Image, ImageDraw, ImageFont, ImageFilter | |
| import numpy as np | |
| import os | |
| import random | |
| import base64 | |
| import requests | |
| from io import BytesIO | |
| # ------------------------- | |
| # FREE HF IMG2IMG MODEL | |
| # ------------------------- | |
| HF_MODEL = "timbrooks/instruct-pix2pix" | |
| HF_API_KEY = os.environ.get("HF_API_KEY") | |
| def hf_img2img(prompt, image_b64): | |
| response = requests.post( | |
| f"https://api-inference.huggingface.co/models/{HF_MODEL}", | |
| headers={"Authorization": f"Bearer {HF_API_KEY}"}, | |
| json={ | |
| "inputs": prompt, | |
| "image": image_b64, | |
| "parameters": {"guidance_scale": 7.5} | |
| }, | |
| timeout=60 | |
| ) | |
| result = response.json() | |
| if isinstance(result, list) and "image" in result[0]: | |
| img_bytes = base64.b64decode(result[0]["image"]) | |
| return Image.open(BytesIO(img_bytes)).convert("RGBA") | |
| return None | |
| # ------------------------- | |
| # FILES & CONSTANTS | |
| # ------------------------- | |
| FRAME_FILES = ["frame_1.png", "frame_2.png", "frame_3.png", "frame_4.png"] | |
| SAINT_FILES = ["saint_base_1.png", "saint_base_2.png", "saint_base_3.png", "saint_base_4.png"] | |
| FRAME_LABELS = ["Frame 1", "Frame 2", "Frame 3", "Frame 4"] | |
| BACKGROUND_MAP = { | |
| "Saint of Drama": "bg_drama.png", | |
| "Saint of Gay Chaos": "bg_chaos.png", | |
| "Saint of Delulu": "bg_delulu.png", | |
| "Divine Gold": "bg_gold.png", | |
| "Infernal Red": "bg_red.png" | |
| } | |
| FONT_PATH = "font.ttf" | |
| BASE_FRAMES = [Image.open(p).convert("RGBA") for p in FRAME_FILES] | |
| BASE_SAINTS = [Image.open(p).convert("RGBA") for p in SAINT_FILES] | |
| FRAME_W, FRAME_H = BASE_FRAMES[0].size | |
| CANVAS_W, CANVAS_H = FRAME_W, FRAME_H | |
| # ------------------------- | |
| # CROPPING | |
| # ------------------------- | |
| def detect_person_bbox_simple(img): | |
| w, h = img.size | |
| return (int(w * 0.18), int(h * 0.05), int(w * 0.82), int(h * 0.75)) | |
| def auto_crop_and_scale(img): | |
| bbox = detect_person_bbox_simple(img) | |
| cropped = img.crop(bbox) | |
| target_h = int(CANVAS_H * 0.62) | |
| scale = target_h / cropped.height | |
| new_w = int(cropped.width * scale) | |
| return cropped.resize((new_w, target_h), Image.LANCZOS) | |
| # ------------------------- | |
| # BACKGROUND REMOVAL (improved) | |
| # ------------------------- | |
| def remove_background_simple(img): | |
| img = img.convert("RGBA") | |
| arr = np.array(img).astype(np.int16) | |
| h, w, _ = arr.shape | |
| samples = [ | |
| arr[5, 5, :3], arr[5, w-6, :3], | |
| arr[h-6, 5, :3], arr[h-6, w-6, :3], | |
| arr[h//2, 5, :3], arr[h//2, w-6, :3] | |
| ] | |
| bg = np.mean(samples, axis=0) | |
| diff = np.linalg.norm(arr[:, :, :3] - bg, axis=2) | |
| mask = diff > 28 | |
| alpha = np.where(mask, 255, 0).astype(np.uint8) | |
| out = np.dstack((arr[:, :, 0].clip(0, 255).astype(np.uint8), | |
| arr[:, :, 1].clip(0, 255).astype(np.uint8), | |
| arr[:, :, 2].clip(0, 255).astype(np.uint8), | |
| alpha)) | |
| return Image.fromarray(out, mode="RGBA") | |
| # ------------------------- | |
| # BACKGROUND | |
| # ------------------------- | |
| def load_background(vibe, color, use_vibe=True): | |
| key = vibe if vibe in BACKGROUND_MAP else color | |
| f = BACKGROUND_MAP.get(key) | |
| if use_vibe and f and os.path.exists(f): | |
| return Image.open(f).convert("RGBA").resize((CANVAS_W, CANVAS_H)) | |
| return Image.new("RGBA", (CANVAS_W, CANVAS_H), (210, 185, 160, 255)) | |
| # ------------------------- | |
| # CARTOONIFY (clean, soft) | |
| # ------------------------- | |
| def cartoonify(img): | |
| img = img.convert("RGB") | |
| smooth = img.filter(ImageFilter.SMOOTH_MORE) | |
| edges = smooth.filter(ImageFilter.EDGE_ENHANCE) | |
| arr = np.array(edges) | |
| arr = (arr // 24) * 24 | |
| return Image.fromarray(arr).convert("RGBA") | |
| # ------------------------- | |
| # TEXT | |
| # ------------------------- | |
| def generate_saint_name(name, sin): | |
| clean = f"SAINT {name.upper()}" if name.strip() else "THE UNNAMED" | |
| return clean, f"PATRON SAINT OF {sin.upper() or 'SIN'}" | |
| def load_font(size): | |
| try: | |
| return ImageFont.truetype(FONT_PATH, size) | |
| except: | |
| return ImageFont.load_default() | |
| def fit_text(draw, text, max_w, base=90): | |
| size = base | |
| while size > 24: | |
| f = load_font(size) | |
| w = draw.textbbox((0, 0), text, font=f)[2] | |
| if w <= max_w - 40: | |
| return f | |
| size -= 4 | |
| return load_font(24) | |
| # ------------------------- | |
| # PREVIEW | |
| # ------------------------- | |
| def compose_preview(img, name, vibe, color, prop, sin, personality, frame_label, remove_bg, cartoon): | |
| frame_idx = FRAME_LABELS.index(frame_label) | |
| frame = BASE_FRAMES[frame_idx] | |
| bg = load_background(vibe, color, use_vibe=remove_bg) | |
| if img: | |
| if remove_bg: | |
| img_no_bg = remove_background_simple(img) | |
| person = auto_crop_and_scale(img_no_bg) | |
| else: | |
| person = auto_crop_and_scale(img) | |
| if cartoon: | |
| person = cartoonify(person) | |
| px = (CANVAS_W - person.width) // 2 | |
| py = int(CANVAS_H * 0.18) | |
| bg.paste(person, (px, py), person) | |
| else: | |
| saint = BASE_SAINTS[frame_idx].copy() | |
| bg.paste(saint, (0, 0), saint) | |
| bg.paste(frame, (0, 0), frame) | |
| saint_name, saint_title = generate_saint_name(name, sin) | |
| d = ImageDraw.Draw(bg) | |
| f1 = fit_text(d, saint_name, CANVAS_W, 80) | |
| f2 = fit_text(d, saint_title, CANVAS_W, 70) | |
| d.text((CANVAS_W // 2, int(CANVAS_H * 0.135)), saint_name, fill="white", anchor="mm", font=f1) | |
| d.text((CANVAS_W // 2, int(CANVAS_H * 0.865)), saint_title, fill="white", anchor="mm", font=f2) | |
| return bg | |
| # ------------------------- | |
| # AI CANONIZER (FREE, WORKING) | |
| # ------------------------- | |
| def canonize_ai(preview_img, vibe, sin, personality): | |
| if preview_img is None: | |
| return None | |
| prompt = ( | |
| f"Make this person look like a glowing saint candle illustration. " | |
| f"Vibe: {vibe}. Sin: {sin}. Personality: {personality}. " | |
| f"Golden halo, divine light, ornate, baroque, dramatic shadows." | |
| ) | |
| buffered = BytesIO() | |
| preview_img.save(buffered, format="PNG") | |
| img_b64 = base64.b64encode(buffered.getvalue()).decode("utf-8") | |
| result = hf_img2img(prompt, img_b64) | |
| return result if result else preview_img | |
| # ------------------------- | |
| # RANDOMIZER | |
| # ------------------------- | |
| VIBE_OPTIONS = ["Saint of Drama", "Saint of Gay Chaos", "Saint of Delulu"] | |
| COLOR_OPTIONS = ["Divine Gold", "Infernal Red", "Celestial Blue", "Pastel Angel", "Neon Drag", "Baroque Sepia"] | |
| PROP_OPTIONS = ["Candles & Incense", "Money & Bills", "Phones & Screens", "Crowns & Halos", "Flowers & Thorns"] | |
| def randomize_settings(img, name, vibe, color, prop, sin, personality, frame_label, remove_bg_flag, cartoon_flag): | |
| new_frame = random.choice(FRAME_LABELS) | |
| new_vibe = random.choice(VIBE_OPTIONS) | |
| new_color = random.choice(COLOR_OPTIONS) | |
| new_prop = random.choice(PROP_OPTIONS) | |
| new_preview = compose_preview( | |
| img, name, new_vibe, new_color, new_prop, sin, personality, | |
| new_frame, remove_bg_flag, cartoon_flag | |
| ) | |
| return new_frame, new_vibe, new_color, new_prop, new_preview | |
| # ------------------------- | |
| # UI | |
| # ------------------------- | |
| with gr.Blocks() as demo: | |
| with gr.Column(): | |
| gr.Image(value="LOGO.png", interactive=False, show_label=False, width=80) | |
| gr.Markdown("## 🔥 Holy Smokes™ Saintifier — AI Edition") | |
| with gr.Row(): | |
| with gr.Column(scale=3): | |
| preview = gr.Image(type="pil", label="Preview") | |
| with gr.Column(scale=2): | |
| uploader = gr.Image(type="pil", label="📸 Upload Photo") | |
| frame_choice = gr.Dropdown(FRAME_LABELS, value="Frame 1", label="🖼 Frame") | |
| remove_bg = gr.Checkbox(label="🧼 Remove Background", value=True) | |
| cartoon = gr.Checkbox(label="🎨 Cartoonify (soft)", value=False) | |
| name = gr.Textbox(label="Name", value="") | |
| sin = gr.Textbox(label="Sin", value="") | |
| personality = gr.Textbox(label="Personality", value="") | |
| vibe = gr.Dropdown(VIBE_OPTIONS, value="Saint of Drama", label="Vibe") | |
| color = gr.Dropdown(COLOR_OPTIONS, value="Divine Gold", label="Color") | |
| prop = gr.Dropdown(PROP_OPTIONS, value="Candles & Incense", label="Prop") | |
| with gr.Row(): | |
| random_btn = gr.Button("🎲 Randomize") | |
| canonize_btn = gr.Button("✨ Canonize with AI") | |
| def update_preview(img, name, vibe, color, prop, sin, personality, frame_label, remove_bg_flag, cartoon_flag): | |
| return compose_preview(img, name, vibe, color, prop, sin, personality, frame_label, remove_bg_flag, cartoon_flag) | |
| for comp in [uploader, name, vibe, color, prop, sin, personality, frame_choice, remove_bg, cartoon]: | |
| comp.change( | |
| update_preview, | |
| [uploader, name, vibe, color, prop, sin, personality, frame_choice, remove_bg, cartoon], | |
| preview | |
| ) | |
| canonize_btn.click( | |
| canonize_ai, | |
| [preview, vibe, sin, personality], | |
| preview | |
| ) | |
| random_btn.click( | |
| randomize_settings, | |
| [uploader, name, vibe, color, prop, sin, personality, frame_choice, remove_bg, cartoon], | |
| [frame_choice, vibe, color, prop, preview] | |
| ) | |
| if __name__ == "__main__": | |
| demo.launch() | |