import gradio as gr
import numpy as np
from PIL import Image, ImageDraw, ImageFilter, ImageOps, ImageFont
import cv2
import torch
import io
import tempfile
import traceback
import time
import os
import random
from typing import List, Tuple, Optional, Dict, Any
import warnings
warnings.filterwarnings("ignore")
# ============================
# CONFIGURAÇÕES
# ============================
MAX_IMAGES = 10
OUTPUT_MAX_SIZE_MB = 1.5
# Presets de resolução
RESOLUTION_PRESETS = {
"Instagram Post (1:1)": (1080, 1080),
"Instagram Story (9:16)": (1080, 1920),
"Facebook Post": (1200, 630),
"Twitter Post": (1200, 675),
"LinkedIn Post": (1200, 627),
"Pinterest Pin": (1000, 1500),
"YouTube Thumbnail": (1280, 720),
"A4 Print (300dpi)": (2480, 3508),
"A3 Print (300dpi)": (3508, 4960),
"Poster 18x24": (2700, 3600),
"Custom": None
}
# Estilos de borda
BORDER_STYLES = {
"Sem Borda": "none",
"Sombra Natural": "natural_shadow",
"Brilho Neon": "neon_glow",
"Contorno Colorido": "colored_outline",
"Borda Dupla": "double_border"
}
# Efeitos de texto
TEXT_EFFECTS = {
"3D Profundo": "deep_3d",
"Neon Glitch": "neon_glitch",
"Metal Brilhante": "shiny_metal",
"Gradiente Colorido": "color_gradient",
"Texto com Sombra": "shadow_text"
}
# Estilos de fonte
FONT_STYLES = {
"Moderno Bold": "modern_bold",
"Futurista": "futuristic",
"Elegante": "elegant",
"Artístico": "artistic",
"Retrô": "retro"
}
class ProcessingLogger:
"""Logger para mostrar progresso ao usuário"""
def __init__(self):
self.logs = []
self.start_time = time.time()
def add_log(self, message: str, status: str = "info", error_details: str = ""):
"""Adiciona um log com timestamp"""
elapsed = time.time() - self.start_time
timestamp = f"[{elapsed:.1f}s]"
icons = {
"info": "📝",
"success": "✅",
"warning": "⚠️",
"error": "❌",
"processing": "🔄",
"debug": "🔍"
}
icon = icons.get(status, "📝")
log_entry = f"{icon} {timestamp} {message}"
if error_details and status == "error":
log_entry += f"\n 🔍 Detalhes: {error_details[:100]}"
self.logs.append(log_entry)
# Manter apenas os últimos 20 logs
if len(self.logs) > 20:
self.logs = self.logs[-20:]
return self.get_logs()
def get_logs(self) -> str:
"""Retorna logs formatados"""
return "\n".join(self.logs)
def clear(self):
"""Limpa logs"""
self.logs = []
self.start_time = time.time()
return "✅ Logs limpos"
class AdvancedCollagePosterGenerator:
def __init__(self):
self.device = "cuda" if torch.cuda.is_available() else "cpu"
self.logger = ProcessingLogger()
self.image_cache = {} # Cache para imagens processadas
print(f"⚙️ Dispositivo: {self.device}")
def precise_background_removal(self, image: Image.Image, image_num: int, file_hash: str) -> Image.Image:
"""Remove fundo com precisão usando contornos de objetos"""
try:
self.logger.add_log(f"Imagem {image_num}: Removendo fundo...", "processing")
# Converter para array numpy
img_array = np.array(image.convert('RGB'))
# Converter para escala de cinza
gray = cv2.cvtColor(img_array, cv2.COLOR_RGB2GRAY)
# Aplicar filtro para reduzir ruído
blurred = cv2.GaussianBlur(gray, (5, 5), 0)
# Detectar bordas
edges = cv2.Canny(blurred, 30, 100)
# Dilatar bordas para fechar contornos
kernel = np.ones((3, 3), np.uint8)
edges = cv2.dilate(edges, kernel, iterations=2)
# Encontrar contornos
contours, _ = cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
if contours:
# Encontrar o maior contorno (objeto principal)
largest_contour = max(contours, key=cv2.contourArea)
# Criar máscara do objeto
mask = np.zeros(gray.shape, np.uint8)
cv2.drawContours(mask, [largest_contour], -1, 255, -1)
# Suavizar máscara
mask = cv2.GaussianBlur(mask, (5, 5), 0)
# Criar imagem com fundo transparente
result = Image.new('RGBA', image.size, (0, 0, 0, 0))
img_rgba = image.convert('RGBA')
# Aplicar máscara
alpha = Image.fromarray(mask)
img_rgba.putalpha(alpha)
result.paste(img_rgba, (0, 0), img_rgba)
self.logger.add_log(f"Imagem {image_num}: Fundo removido com precisão", "success")
return result
else:
# Fallback para método simples
return self.simple_background_removal(image, image_num)
except Exception as e:
error_msg = str(e)
self.logger.add_log(f"Imagem {image_num}: Erro na remoção: {error_msg[:50]}", "error")
return self.simple_background_removal(image, image_num)
def simple_background_removal(self, image: Image.Image, image_num: int) -> Image.Image:
"""Método simples de remoção de fundo (fallback)"""
try:
# Converter para RGBA
img = image.convert("RGBA")
data = np.array(img)
# Detectar cor dominante nos cantos (assumindo fundo)
h, w, _ = data.shape
corner_samples = []
sample_size = min(20, h//10, w//10)
if sample_size > 0:
corners = [
data[:sample_size, :sample_size],
data[:sample_size, -sample_size:],
data[-sample_size:, :sample_size],
data[-sample_size:, -sample_size:]
]
for corner in corners:
if corner.size > 0:
corner_samples.append(corner.mean(axis=(0, 1)))
if corner_samples:
bg_color = np.mean(corner_samples, axis=0)
# Calcular diferença de cor
color_diff = np.sqrt(np.sum((data[:, :, :3] - bg_color) ** 2, axis=2))
# Normalizar e criar máscara
max_diff = np.max(color_diff) if np.max(color_diff) > 0 else 1
normalized_diff = color_diff / max_diff
threshold = 0.15
mask = normalized_diff > threshold
# Suavizar máscara
mask = mask.astype(np.uint8) * 255
mask = cv2.GaussianBlur(mask, (7, 7), 0)
# Aplicar máscara
data[:, :, 3] = mask
result = Image.fromarray(data, 'RGBA')
self.logger.add_log(f"Imagem {image_num}: Fundo removido (método simples)", "warning")
return result
except Exception as e:
self.logger.add_log(f"Imagem {image_num}: Fallback falhou, mantendo original", "error")
return image.convert("RGBA")
def apply_border_effect(self, image: Image.Image, style: str, color: str,
image_num: int, outline_thickness: int = 3) -> Image.Image:
"""Aplica efeitos de borda avançados"""
if style == "none":
return image
try:
self.logger.add_log(f"Imagem {image_num}: Aplicando efeito '{style}'...", "processing")
if image.mode != 'RGBA':
image = image.convert('RGBA')
# Converter cor hex para RGB
try:
hex_color = color.strip().lstrip('#').upper()
if len(hex_color) == 3:
hex_color = ''.join([c*2 for c in hex_color])
if len(hex_color) != 6:
raise ValueError("Cor hexadecimal inválida")
rgb = (
int(hex_color[0:2], 16),
int(hex_color[2:4], 16),
int(hex_color[4:6], 16)
)
except:
self.logger.add_log(f"⚠️ Cor inválida: {color}, usando azul padrão", "warning")
rgb = (0, 120, 255)
if style == "natural_shadow":
# Sombra natural que segue o contorno
shadow_size = 20
expanded = Image.new('RGBA',
(image.width + shadow_size, image.height + shadow_size),
(0, 0, 0, 0))
# Extrair máscara alpha
alpha = np.array(image.split()[-1])
# Criar sombra
kernel = np.ones((5, 5), np.uint8)
dilated = cv2.dilate(alpha, kernel, iterations=2)
shadow_mask = cv2.GaussianBlur(dilated, (15, 15), 5)
# Criar sombra colorida
shadow_layer = np.zeros((*shadow_mask.shape, 4), dtype=np.uint8)
shadow_layer[..., 0] = max(0, rgb[0] - 50)
shadow_layer[..., 1] = max(0, rgb[1] - 50)
shadow_layer[..., 2] = max(0, rgb[2] - 50)
shadow_layer[..., 3] = shadow_mask * 0.3
shadow_img = Image.fromarray(shadow_layer, 'RGBA')
# Colar sombra e imagem
expanded.paste(shadow_img, (8, 8), shadow_img)
expanded.paste(image, (0, 0), image)
result = expanded
elif style == "neon_glow":
# Brilho neon
glow_size = 25
expanded = Image.new('RGBA',
(image.width + glow_size*2, image.height + glow_size*2),
(0, 0, 0, 0))
alpha = np.array(image.split()[-1])
# Criar camadas de brilho
for i in range(3):
layer_size = glow_size - i * 5
kernel = np.ones((layer_size, layer_size), np.uint8)
dilated = cv2.dilate(alpha, kernel, iterations=1)
if i > 0:
prev_kernel = np.ones((layer_size + 5, layer_size + 5), np.uint8)
prev_dilated = cv2.dilate(alpha, prev_kernel, iterations=1)
layer_mask = dilated - prev_dilated
else:
layer_mask = dilated
# Suavizar
layer_mask = cv2.GaussianBlur(layer_mask, (5, 5), 2)
# Criar camada
glow_color = (
min(255, rgb[0] + i * 20),
min(255, rgb[1] + i * 20),
min(255, rgb[2] + i * 20)
)
glow_layer = Image.new('RGBA', expanded.size, glow_color + (80 - i*20,))
glow_layer.putalpha(Image.fromarray(layer_mask))
expanded.paste(glow_layer, (glow_size, glow_size), glow_layer)
expanded.paste(image, (glow_size, glow_size), image)
result = expanded
elif style == "colored_outline":
# Contorno colorido com espessura ajustável
outline_thickness = max(1, min(outline_thickness, 10))
expanded = Image.new('RGBA',
(image.width + outline_thickness*2,
image.height + outline_thickness*2),
(0, 0, 0, 0))
alpha = np.array(image.split()[-1])
# Dilatar para criar contorno
kernel_size = outline_thickness * 2 + 1
kernel = np.ones((kernel_size, kernel_size), np.uint8)
dilated = cv2.dilate(alpha, kernel, iterations=1)
# Subtrair original para obter contorno
outline_mask = dilated - alpha
# Suavizar
if outline_thickness > 1:
outline_mask = cv2.GaussianBlur(outline_mask, (3, 3), 1)
# Criar contorno
outline_img = Image.new('RGBA', expanded.size, rgb)
outline_img.putalpha(Image.fromarray(outline_mask))
expanded.paste(outline_img, (0, 0), outline_img)
expanded.paste(image, (outline_thickness, outline_thickness), image)
result = expanded
elif style == "double_border":
# Borda dupla
border = 15
expanded = Image.new('RGBA',
(image.width + border*2, image.height + border*2),
(0, 0, 0, 0))
draw = ImageDraw.Draw(expanded)
# Cor complementar
complement = (255 - rgb[0], 255 - rgb[1], 255 - rgb[2])
# Borda externa
draw.rectangle([0, 0, expanded.width-1, expanded.height-1],
outline=rgb, width=border//2)
# Borda interna
draw.rectangle([border//2, border//2,
expanded.width-border//2-1, expanded.height-border//2-1],
outline=complement, width=border//4)
expanded.paste(image, (border, border), image)
result = expanded
self.logger.add_log(f"Imagem {image_num}: Efeito '{style}' aplicado", "success")
return result
except Exception as e:
error_msg = str(e)
self.logger.add_log(f"Imagem {image_num}: Erro na borda: {error_msg[:50]}", "error")
return image
def create_gradient_background(self, size: Tuple[int, int], color1: str, color2: str) -> Image.Image:
"""Cria fundo degradê"""
try:
self.logger.add_log("Criando fundo degradê...", "processing")
width, height = size
# Validar e converter cores
def validate_color(color_str):
try:
hex_color = color_str.strip().lstrip('#').upper()
if len(hex_color) == 3:
hex_color = ''.join([c*2 for c in hex_color])
if len(hex_color) != 6:
raise ValueError
return (
int(hex_color[0:2], 16),
int(hex_color[2:4], 16),
int(hex_color[4:6], 16)
)
except:
return (102, 126, 234) # Azul padrão
rgb1 = validate_color(color1)
rgb2 = validate_color(color2)
# Criar fundo
background = Image.new('RGB', size)
draw = ImageDraw.Draw(background)
# Gradiente vertical
for y in range(height):
ratio = y / height
r = int(rgb1[0] * (1 - ratio) + rgb2[0] * ratio)
g = int(rgb1[1] * (1 - ratio) + rgb2[1] * ratio)
b = int(rgb1[2] * (1 - ratio) + rgb2[2] * ratio)
draw.line([(0, y), (width, y)], fill=(r, g, b))
# Suavizar
background = background.filter(ImageFilter.GaussianBlur(radius=0.5))
self.logger.add_log("Fundo degradê criado", "success")
return background
except Exception as e:
error_msg = str(e)
self.logger.add_log(f"Erro no fundo: {error_msg[:50]}", "error")
return Image.new('RGB', size, (200, 200, 200))
def apply_text_effect(self, draw, text: str, position: Tuple[int, int],
font: ImageFont.FreeTypeFont, effect: str, color: str) -> None:
"""Aplica efeitos de texto"""
# Validar cor
try:
hex_color = color.strip().lstrip('#').upper()
if len(hex_color) == 3:
hex_color = ''.join([c*2 for c in hex_color])
if len(hex_color) != 6:
raise ValueError
text_rgb = (
int(hex_color[0:2], 16),
int(hex_color[2:4], 16),
int(hex_color[4:6], 16)
)
except:
text_rgb = (255, 255, 255)
x, y = position
if effect == "deep_3d":
# Efeito 3D
for i in range(5, 0, -1):
shadow = (text_rgb[0]//(i+1), text_rgb[1]//(i+1), text_rgb[2]//(i+1))
draw.text((x + i, y + i), text, font=font, fill=shadow)
draw.text((x, y), text, font=font, fill=text_rgb)
elif effect == "neon_glitch":
# Neon glitch
offsets = [(2, 0), (-2, 0), (0, 2)]
colors = [(0, 255, 255), (255, 0, 255), (255, 255, 0)]
for (dx, dy), glow in zip(offsets, colors):
draw.text((x + dx, y + dy), text, font=font, fill=glow)
draw.text((x, y), text, font=font, fill=text_rgb)
elif effect == "shiny_metal":
# Metal brilhante
for i in range(3):
shade = 180 - i * 30
draw.text((x - i, y - i), text, font=font, fill=(shade, shade, shade))
draw.text((x, y), text, font=font, fill=(255, 255, 200))
elif effect == "color_gradient":
# Gradiente
for i, char in enumerate(text):
ratio = i / max(len(text)-1, 1)
r = int(text_rgb[0] * (1 - ratio) + 255 * ratio)
g = int(text_rgb[1] * ratio + 128 * (1 - ratio))
b = int(text_rgb[2] * (1 - ratio) + 255 * ratio)
char_x = x + draw.textlength(text[:i], font=font)
draw.text((char_x, y), char, font=font, fill=(r, g, b))
else: # shadow_text
# Sombra clássica
shadow = (text_rgb[0]//3, text_rgb[1]//3, text_rgb[2]//3)
draw.text((x + 2, y + 2), text, font=font, fill=shadow)
draw.text((x, y), text, font=font, fill=text_rgb)
def add_text_with_effects(self, image: Image.Image, title: str, description: str,
text_color: str, text_effect: str, font_style: str) -> Image.Image:
"""Adiciona texto com efeitos"""
try:
if not title and not description:
return image
self.logger.add_log("Adicionando texto...", "processing")
if image.mode != 'RGBA':
image = image.convert('RGBA')
text_layer = Image.new('RGBA', image.size, (0, 0, 0, 0))
draw = ImageDraw.Draw(text_layer)
width, height = image.size
margin = int(min(width, height) * 0.1)
# Carregar fontes
try:
title_size = int(min(width, height) * 0.07)
desc_size = int(min(width, height) * 0.035)
font_paths = {
"modern_bold": "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf",
"futuristic": "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf",
"elegant": "/usr/share/fonts/truetype/liberation/LiberationSerif-Bold.ttf",
"artistic": "/usr/share/fonts/truetype/ubuntu/Ubuntu-B.ttf",
"retro": "/usr/share/fonts/truetype/liberation/LiberationMono-Bold.ttf"
}
font_path = font_paths.get(font_style, "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf")
title_font = ImageFont.truetype(font_path, title_size)
desc_font = ImageFont.truetype(font_path, desc_size)
except:
title_font = ImageFont.load_default()
desc_font = ImageFont.load_default()
# Posicionar texto de baixo para cima
y_pos = height - margin
# Descrição
if description and description.strip():
words = description.split()
lines = []
current = []
for word in words:
current.append(word)
test = ' '.join(current)
try:
bbox = draw.textbbox((0, 0), test, font=desc_font)
w = bbox[2] - bbox[0]
except:
w = len(test) * desc_size * 0.6
if w > (width - 2 * margin):
if len(current) > 1:
lines.append(' '.join(current[:-1]))
current = [word]
else:
lines.append(word)
current = []
if current:
lines.append(' '.join(current))
lines.reverse()
for line in lines:
try:
bbox = draw.textbbox((0, 0), line, font=desc_font)
w = bbox[2] - bbox[0]
h = bbox[3] - bbox[1]
except:
w = len(line) * desc_size * 0.6
h = desc_size
x = (width - w) // 2
y_pos -= h + 10
self.apply_text_effect(draw, line, (x, y_pos), desc_font, text_effect, text_color)
# Título
if title and title.strip():
if description and description.strip():
y_pos -= int(title_size * 0.5)
words = title.split()
lines = []
current = []
for word in words:
current.append(word)
test = ' '.join(current)
try:
bbox = draw.textbbox((0, 0), test, font=title_font)
w = bbox[2] - bbox[0]
except:
w = len(test) * title_size * 0.6
if w > (width - 2 * margin):
if len(current) > 1:
lines.append(' '.join(current[:-1]))
current = [word]
else:
lines.append(word)
current = []
if current:
lines.append(' '.join(current))
lines.reverse()
for line in lines:
try:
bbox = draw.textbbox((0, 0), line, font=title_font)
w = bbox[2] - bbox[0]
h = bbox[3] - bbox[1]
except:
w = len(line) * title_size * 0.6
h = title_size
x = (width - w) // 2
y_pos -= h + 20
self.apply_text_effect(draw, line, (x, y_pos), title_font, text_effect, text_color)
result = Image.alpha_composite(image, text_layer)
self.logger.add_log("Texto adicionado", "success")
return result
except Exception as e:
error_msg = str(e)
self.logger.add_log(f"Erro no texto: {error_msg[:50]}", "error")
return image
def create_collage(self, images: List[Image.Image], background: Image.Image) -> Image.Image:
"""Cria collage harmoniosa"""
try:
self.logger.add_log("Criando collage...", "processing")
result = background.copy().convert('RGBA')
bg_width, bg_height = background.size
if not images:
return result.convert('RGB')
num_images = len(images)
if num_images == 1:
img = images[0]
img.thumbnail((bg_width // 2, bg_height // 2), Image.Resampling.LANCZOS)
x = (bg_width - img.width) // 2
y = (bg_height - img.height) // 3
result.paste(img, (x, y), img)
elif num_images == 2:
for idx, img in enumerate(images):
scale = 0.6 if idx == 0 else 0.5
img.thumbnail((int(bg_width * scale), int(bg_height * scale)), Image.Resampling.LANCZOS)
if idx == 0:
x = bg_width // 4 - img.width // 2
y = bg_height // 3 - img.height // 2
else:
x = bg_width * 2 // 3 - img.width // 2
y = bg_height * 2 // 3 - img.height // 2
result.paste(img, (x, y), img)
elif num_images == 3:
positions = [
(bg_width // 3, bg_height // 4),
(bg_width * 2 // 3, bg_height // 4),
(bg_width // 2, bg_height * 2 // 3)
]
for idx, (img, (px, py)) in enumerate(zip(images, positions)):
scale = 0.5 - idx * 0.05
img.thumbnail((int(bg_width * scale), int(bg_height * scale)), Image.Resampling.LANCZOS)
x = px - img.width // 2
y = py - img.height // 2
# Rotação leve
if idx % 2 == 0:
img = img.rotate(5, expand=True, fillcolor=(0, 0, 0, 0))
else:
img = img.rotate(-5, expand=True, fillcolor=(0, 0, 0, 0))
result.paste(img, (x, y), img)
else:
cols = min(3, int(np.ceil(np.sqrt(num_images))))
rows = int(np.ceil(num_images / cols))
cell_w = bg_width // (cols + 1)
cell_h = bg_height // (rows + 2)
for idx, img in enumerate(images[:cols*rows]):
row = idx // cols
col = idx % cols
scale = 0.8 - row * 0.1
img.thumbnail((int(cell_w * scale), int(cell_h * scale)), Image.Resampling.LANCZOS)
x = (col + 1) * cell_w - img.width // 2
y = (row + 1) * cell_h - img.height // 2
# Pequena rotação aleatória
if random.random() > 0.7:
rot = random.randint(-8, 8)
img = img.rotate(rot, expand=True, fillcolor=(0, 0, 0, 0))
result.paste(img, (x, y), img)
self.logger.add_log("Collage criada", "success")
return result.convert('RGB')
except Exception as e:
error_msg = str(e)
self.logger.add_log(f"Erro na collage: {error_msg[:50]}", "error")
return background.convert('RGB')
def optimize_file_size(self, image: Image.Image) -> Image.Image:
"""Otimiza tamanho do arquivo"""
try:
self.logger.add_log("Otimizando arquivo...", "processing")
if image.mode == 'RGBA':
image = image.convert('RGB')
# Tentar diferentes qualidades
for quality in [90, 85, 80, 75, 70]:
buffer = io.BytesIO()
image.save(buffer, format='JPEG', quality=quality, optimize=True)
size_mb = len(buffer.getvalue()) / (1024 * 1024)
if size_mb <= OUTPUT_MAX_SIZE_MB:
buffer.seek(0)
result = Image.open(buffer).copy()
self.logger.add_log(f"Arquivo: {size_mb:.2f}MB (qualidade: {quality}%)", "success")
return result
# Fallback
buffer = io.BytesIO()
image.save(buffer, format='JPEG', quality=70, optimize=True)
buffer.seek(0)
return Image.open(buffer).copy()
except Exception as e:
error_msg = str(e)
self.logger.add_log(f"Erro na otimização: {error_msg[:50]}", "error")
return image
def generate_poster(self, image_files, resolution_preset, custom_width, custom_height,
border_style, border_color, gradient_color1, gradient_color2,
title, description, text_color, text_effect, font_style, outline_thickness):
"""Função principal para gerar o pôster"""
# Limpar cache antigo
self.image_cache.clear()
self.logger.clear()
try:
self.logger.add_log("🚀 INICIANDO GERAÇÃO DE PÔSTER", "info")
if not image_files:
self.logger.add_log("❌ Nenhuma imagem fornecida", "error")
return None, "❌ Por favor, faça upload de imagens", self.logger.get_logs()
self.logger.add_log(f"📸 {len(image_files)} imagem(ns) carregada(s)", "success")
# Determinar resolução
if resolution_preset == "Custom":
try:
size = (int(custom_width), int(custom_height))
self.logger.add_log(f"📐 Custom: {size[0]}x{size[1]}px", "info")
except:
self.logger.add_log("❌ Dimensões inválidas", "error")
return None, "❌ Use números válidos", self.logger.get_logs()
else:
size = RESOLUTION_PRESETS.get(resolution_preset, (1080, 1080))
self.logger.add_log(f"📐 {resolution_preset}: {size[0]}x{size[1]}px", "info")
# Processar imagens
processed_images = []
for idx, file_info in enumerate(image_files[:MAX_IMAGES]):
try:
filepath = file_info if isinstance(file_info, str) else file_info.name
self.logger.add_log(f"🔄 Processando imagem {idx+1}...", "processing")
# Abrir imagem
img = Image.open(filepath)
if img.mode != 'RGB':
img = img.convert('RGB')
# Gerar hash do arquivo para cache
import hashlib
with open(filepath, 'rb') as f:
file_hash = hashlib.md5(f.read()).hexdigest()[:8]
# Remover fundo
bg_removed = self.precise_background_removal(img, idx+1, file_hash)
# Aplicar borda
if border_style != "Sem Borda":
border_key = BORDER_STYLES[border_style]
bordered = self.apply_border_effect(bg_removed, border_key, border_color,
idx+1, outline_thickness)
processed_images.append(bordered)
else:
processed_images.append(bg_removed)
self.logger.add_log(f"✅ Imagem {idx+1} processada", "success")
except Exception as e:
error_msg = str(e)
self.logger.add_log(f"⚠️ Imagem {idx+1} ignorada: {error_msg[:50]}", "warning")
continue
if not processed_images:
self.logger.add_log("❌ Nenhuma imagem processada", "error")
return None, "❌ Não foi possível processar imagens", self.logger.get_logs()
self.logger.add_log(f"✅ {len(processed_images)} imagem(s) processada(s)", "success")
# Criar fundo
self.logger.add_log("🎨 Criando fundo...", "processing")
background = self.create_gradient_background(size, gradient_color1, gradient_color2)
# Criar collage
self.logger.add_log("🖼️ Criando collage...", "processing")
collage = self.create_collage(processed_images, background)
# Adicionar texto
if title or description:
self.logger.add_log("✍️ Adicionando texto...", "processing")
collage = self.add_text_with_effects(collage, title, description,
text_color, text_effect, font_style)
# Otimizar
self.logger.add_log("🗜️ Otimizando...", "processing")
final_image = self.optimize_file_size(collage)
# Finalizar
self.logger.add_log("🎉 PÔSTER GERADO COM SUCESSO!", "success")
success_msg = f"✅ Pôster pronto! {len(processed_images)} imagem(s), {size[0]}x{size[1]}px"
return final_image, success_msg, self.logger.get_logs()
except Exception as e:
error_msg = str(e)
self.logger.add_log(f"❌ ERRO CRÍTICO: {error_msg[:100]}", "error")
return None, f"❌ Erro: {error_msg[:100]}", self.logger.get_logs()
# ============================
# INTERFACE GRADIO
# ============================
# Inicializar gerador
generator = AdvancedCollagePosterGenerator()
def format_logs_for_html(log_text):
"""Formata logs para HTML"""
lines = log_text.split('\n')
formatted = []
for line in lines:
if '❌' in line:
formatted.append(f'{line}')
elif '✅' in line:
formatted.append(f'{line}')
elif '⚠️' in line:
formatted.append(f'{line}')
elif '🔄' in line:
formatted.append(f'{line}')
elif '🚀' in line or '🎉' in line:
formatted.append(f'{line}')
elif '📸' in line or '📐' in line:
formatted.append(f'{line}')
elif '🎨' in line or '🖼️' in line:
formatted.append(f'{line}')
elif '✍️' in line:
formatted.append(f'{line}')
elif '🗜️' in line:
formatted.append(f'{line}')
else:
formatted.append(f'{line}')
return '
'.join(formatted)
with gr.Blocks(theme=gr.themes.Soft(), title="Gerador de Pôster de Colagem") as demo:
gr.Markdown("""
# 🎨 Gerador de Pôster de Colagem
Crie pôsteres incríveis com múltiplas imagens!
""")
with gr.Row():
with gr.Column(scale=1):
# Upload de imagens - SIMPLIFICADO
gr.Markdown("### 📤 Upload de Imagens")
image_input = gr.File(
label="Selecione imagens (até 10)",
file_types=["image"],
file_count="multiple",
type="filepath",
height=100
)
# Apenas mostra quantas imagens foram carregadas
image_counter = gr.Markdown("**Nenhuma imagem carregada**")
# Configurações
gr.Markdown("### ⚙️ Configurações")
with gr.Group():
resolution_preset = gr.Dropdown(
label="Tamanho do Pôster",
choices=list(RESOLUTION_PRESETS.keys()),
value="Instagram Post (1:1)"
)
with gr.Row(visible=False) as custom_row:
custom_width = gr.Number(label="Largura", value=1080)
custom_height = gr.Number(label="Altura", value=1080)
with gr.Group():
border_style = gr.Dropdown(
label="Estilo da Borda",
choices=list(BORDER_STYLES.keys()),
value="Sombra Natural"
)
with gr.Row():
border_color = gr.ColorPicker(
label="Cor da Borda",
value="#FF6B6B"
)
outline_thickness = gr.Slider(
label="Espessura do Contorno",
minimum=1,
maximum=10,
value=3,
step=1,
visible=False
)
with gr.Group():
gr.Markdown("### 🌈 Cores do Fundo")
with gr.Row():
gradient_color1 = gr.ColorPicker(
label="Cor Superior",
value="#667EEA"
)
gradient_color2 = gr.ColorPicker(
label="Cor Inferior",
value="#764BA2"
)
with gr.Group():
gr.Markdown("### ✨ Texto")
with gr.Row():
text_effect = gr.Dropdown(
label="Efeito do Texto",
choices=list(TEXT_EFFECTS.keys()),
value="3D Profundo"
)
font_style = gr.Dropdown(
label="Estilo da Fonte",
choices=list(FONT_STYLES.keys()),
value="Moderno Bold"
)
text_color = gr.ColorPicker(
label="Cor do Texto",
value="#FFFFFF"
)
title_input = gr.Textbox(
label="Título (opcional)",
placeholder="Digite o título..."
)
description_input = gr.Textbox(
label="Descrição (opcional)",
placeholder="Digite a descrição...",
lines=2
)
# Botões
with gr.Row():
process_btn = gr.Button(
"🚀 Gerar Pôster",
variant="primary",
size="lg"
)
clear_btn = gr.Button(
"🗑️ Limpar Tudo",
variant="secondary"
)
with gr.Column(scale=2):
# Resultado
gr.Markdown("### 🖼️ Pôster Gerado")
output_image = gr.Image(
label="Resultado",
type="pil",
height=500
)
# Status
status_text = gr.Markdown("""
**Status:** Aguardando imagens...
**Instruções:**
1. Selecione imagens
2. Configure as opções
3. Clique em 'Gerar Pôster'
""")
# Logs
logs_display = gr.HTML(
label="📋 Logs",
value='