HolySmokes1234's picture
Update app.py
5ec4069 verified
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()