sky-replacer / app.py
Daniel251's picture
Update app.py
b11ee2c verified
Raw
History Blame Contribute Delete
39.1 kB
"""
🌤️ Sky Replacer
Substitui o céu em imagens com opções profissionais
Autor: Daniel251
Versão: 2.0.0
Data: 2026
"""
import gradio as gr
from PIL import Image, ImageDraw, ImageFilter, ImageEnhance
import numpy as np
import cv2
import tempfile
import time
import os
import sys
import math
import random
import colorsys
from io import BytesIO
import warnings
warnings.filterwarnings('ignore')
print("=" * 60)
print("🚀 INICIANDO APP 10: SKY REPLACER")
print(f"Python version: {sys.version}")
print(f"Pillow version: {Image.__version__}")
print("=" * 60)
# ========== CONFIGURAÇÃO ==========
MAX_IMAGE_SIZE = 1024
SKY_LIBRARY = {
"sunny_day": "☀️ Dia Ensolarado",
"blue_sky": "🔵 Céu Azul Limpo",
"cloudy": "☁️ Céu Nublado",
"stormy": "⛈️ Tempestade",
"sunset": "🌅 Pôr do Sol",
"sunrise": "🌇 Nascer do Sol",
"golden_hour": "✨ Hora Dourada",
"night_sky": "🌙 Noite Estrelada",
"northern_lights": "🌈 Aurora Boreal",
"space": "🚀 Espaço Sideral",
"foggy": "🌫️ Nebuloso",
"rainbow": "🌈 Arco-Íris",
"dramatic": "🎭 Dramático",
"pastel": "🎨 Pastel",
"gradient": "🔄 Gradiente"
}
print(f"📏 Tamanho máximo: {MAX_IMAGE_SIZE}px")
print(f"🌤️ Céus disponíveis: {len(SKY_LIBRARY)}")
# ========== FUNÇÕES AUXILIARES ==========
def log_message(message):
"""Log para debug"""
timestamp = time.strftime("%H:%M:%S")
print(f"[{timestamp}] {message}")
def validate_image(image):
"""Valida e prepara imagem"""
try:
if image is None:
return None, "❌ Nenhuma imagem fornecida"
# Converter para PIL Image
if isinstance(image, np.ndarray):
img = Image.fromarray(image)
elif isinstance(image, Image.Image):
img = image
else:
return None, "❌ Tipo de imagem desconhecido"
# Verificar tamanho
if max(img.size) > 4000:
img = resize_image(img, 4000)
log_message(f"Imagem redimensionada para {img.size}")
if min(img.size) < 50:
return None, "❌ Imagem muito pequena (<50px)."
log_message(f"✅ Imagem válida: {img.size}px, {img.mode}")
return img, "ok"
except Exception as e:
error_msg = f"❌ Erro na validação: {str(e)}"
log_message(error_msg)
return None, error_msg
def resize_image(image, max_size):
"""Redimensiona mantendo aspect ratio"""
if max(image.size) <= max_size:
return image
ratio = max_size / max(image.size)
new_width = int(image.width * ratio)
new_height = int(image.height * ratio)
log_message(f"Redimensionando: {image.size} -> ({new_width}, {new_height})")
return image.resize((new_width, new_height), Image.Resampling.LANCZOS)
def detect_sky_region(image):
"""Detecta automaticamente a região do céu na imagem"""
try:
# Converter para array numpy
img_array = np.array(image.convert('RGB'))
height, width = img_array.shape[:2]
# Método 1: Baseado em cores (azul/branco)
hsv = cv2.cvtColor(img_array, cv2.COLOR_RGB2HSV)
# Definição de cores de céu
lower_blue1 = np.array([90, 50, 50])
upper_blue1 = np.array([130, 255, 255])
lower_blue2 = np.array([100, 40, 40])
upper_blue2 = np.array([140, 255, 255])
# Céu nublado (branco/cinza)
lower_white = np.array([0, 0, 180])
upper_white = np.array([180, 50, 255])
# Criar máscaras
mask_blue1 = cv2.inRange(hsv, lower_blue1, upper_blue1)
mask_blue2 = cv2.inRange(hsv, lower_blue2, upper_blue2)
mask_white = cv2.inRange(hsv, lower_white, upper_white)
# Combinar máscaras
sky_mask = cv2.bitwise_or(mask_blue1, mask_blue2)
sky_mask = cv2.bitwise_or(sky_mask, mask_white)
# Método 2: Baseado em posição (céu geralmente está no topo)
position_mask = np.zeros((height, width), dtype=np.uint8)
for y in range(height):
weight = max(0, 1.0 - (y / height) * 1.5)
position_mask[y, :] = int(255 * weight)
# Método 3: Detecção de bordas para encontrar horizonte
gray = cv2.cvtColor(img_array, cv2.COLOR_RGB2GRAY)
edges = cv2.Canny(gray, 50, 150)
lines = cv2.HoughLinesP(edges, 1, np.pi/180, threshold=50,
minLineLength=width//4, maxLineGap=10)
horizon_y = height * 0.3
if lines is not None:
horizontal_lines = []
for line in lines:
x1, y1, x2, y2 = line[0]
angle = abs(np.arctan2(y2-y1, x2-x1) * 180 / np.pi)
if abs(angle) < 10 or abs(angle - 180) < 10:
horizontal_lines.append(min(y1, y2))
if horizontal_lines:
horizon_y = np.median(horizontal_lines)
# Criar máscara final
combined_mask = cv2.addWeighted(sky_mask, 0.6, position_mask, 0.4, 0)
combined_mask[int(horizon_y):, :] = 0
# Operações morfológicas para limpar
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5,5))
combined_mask = cv2.morphologyEx(combined_mask, cv2.MORPH_CLOSE, kernel)
combined_mask = cv2.morphologyEx(combined_mask, cv2.MORPH_OPEN, kernel)
# Preencher buracos e suavizar
combined_mask = cv2.morphologyEx(combined_mask, cv2.MORPH_CLOSE,
cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (15,15)))
combined_mask = cv2.GaussianBlur(combined_mask, (11, 11), 0)
# Se a máscara for muito pequena, usar padrão
if np.sum(combined_mask > 127) < 100:
log_message("⚠️ Céu não detectado automaticamente, usando método padrão")
combined_mask = np.zeros((height, width), dtype=np.uint8)
combined_mask[:int(height*0.4), :] = 255
sky_percentage = np.sum(combined_mask > 127) / (width * height) * 100
log_message(f"✅ Céu detectado: {sky_percentage:.1f}% da imagem")
return combined_mask
except Exception as e:
log_message(f"❌ Erro na detecção de céu: {str(e)}")
height, width = image.size[1], image.size[0]
mask = np.zeros((height, width), dtype=np.uint8)
mask[:int(height*0.4), :] = 255
return mask
# ========== GERADORES DE CÉU ==========
def create_sunny_sky(size):
"""Cria céu de dia ensolarado"""
width, height = size
img = Image.new('RGB', size, (135, 206, 235))
draw = ImageDraw.Draw(img)
# Gradiente de azul
for y in range(height):
ratio = y / height
r = int(135 * (1 - ratio) + 255 * ratio)
g = int(206 * (1 - ratio) + 250 * ratio)
b = int(235 * (1 - ratio) + 255 * ratio)
draw.line([(0, y), (width, y)], fill=(r, g, b))
# Adicionar sol
sun_size = min(width, height) // 10
sun_x = width * 0.8
sun_y = height * 0.2
# Brilho do sol
for i in range(5, 0, -1):
radius = sun_size * (1 + i * 0.3)
temp_img = Image.new('RGBA', size, (0, 0, 0, 0))
temp_draw = ImageDraw.Draw(temp_img)
alpha = int(50 / i)
temp_draw.ellipse([sun_x-radius, sun_y-radius, sun_x+radius, sun_y+radius],
fill=(255, 255, 200, alpha))
img = Image.alpha_composite(img.convert('RGBA'), temp_img).convert('RGB')
# Sol principal
sun_img = Image.new('RGBA', size, (0, 0, 0, 0))
sun_draw = ImageDraw.Draw(sun_img)
sun_draw.ellipse([sun_x-sun_size, sun_y-sun_size, sun_x+sun_size, sun_y+sun_size],
fill=(255, 255, 100), outline=(255, 200, 50), width=3)
img = Image.alpha_composite(img.convert('RGBA'), sun_img).convert('RGB')
# Adicionar nuvens simples
img = add_simple_clouds(img, count=5)
return img
def create_blue_sky(size):
"""Cria céu azul limpo"""
width, height = size
img = Image.new('RGB', size, (0, 0, 0))
# Gradiente azul profundo
for y in range(height):
ratio = y / height
r = int(30 * ratio)
g = int(144 * (0.7 + 0.3 * ratio))
b = int(255 * (0.8 + 0.2 * ratio))
for x in range(width):
variation = math.sin(x * 0.01) * 0.05 + math.cos(y * 0.005) * 0.03
vr = int(r * (1 + variation))
vg = int(g * (1 + variation))
vb = int(b * (1 + variation))
img.putpixel((x, y), (vr, vg, vb))
return img
def create_cloudy_sky(size):
"""Cria céu nublado"""
width, height = size
img = Image.new('RGB', size, (200, 220, 240))
# Base de nuvens
for y in range(height):
ratio = y / height
base_r = 200 + int(30 * ratio)
base_g = 220 + int(20 * ratio)
base_b = 240 + int(10 * ratio)
for x in range(width):
noise = (math.sin(x * 0.01) + math.sin(y * 0.007) +
math.sin((x + y) * 0.005)) / 3
r = max(100, min(255, base_r + int(noise * 40)))
g = max(120, min(255, base_g + int(noise * 30)))
b = max(140, min(255, base_b + int(noise * 20)))
img.putpixel((x, y), (r, g, b))
# Adicionar mais nuvens
img = add_simple_clouds(img, count=8, cloud_type="cumulus")
return img
def create_stormy_sky(size):
"""Cria céu de tempestade"""
width, height = size
img = Image.new('RGB', size, (50, 50, 70))
draw = ImageDraw.Draw(img)
# Gradiente dramático
for y in range(height):
ratio = y / height
r = int(30 + 70 * ratio)
g = int(30 + 60 * ratio)
b = int(50 + 80 * ratio)
draw.line([(0, y), (width, y)], fill=(r, g, b))
# Nuvens escuras
img_array = np.array(img)
for i in range(5):
cloud_x = random.randint(0, width)
cloud_y = random.randint(0, height // 2)
cloud_size = random.randint(width // 4, width // 2)
# Escurecer área da nuvem
for dy in range(-cloud_size//2, cloud_size//2):
for dx in range(-cloud_size//2, cloud_size//2):
x = cloud_x + dx
y = cloud_y + dy
if 0 <= x < width and 0 <= y < height:
dist = math.sqrt(dx*dx + dy*dy)
if dist < cloud_size // 2:
darkness = 1.0 - (dist / (cloud_size // 2)) * 0.7
img_array[y, x] = (img_array[y, x] * darkness).astype(np.uint8)
img = Image.fromarray(img_array)
return img
def create_sunset_sky(size):
"""Cria céu de pôr do sol"""
width, height = size
img = Image.new('RGB', size, (0, 0, 0))
# Gradiente de pôr do sol
for y in range(height):
ratio = y / height
if ratio < 0.3:
r = 255
g = int(100 + 100 * (ratio / 0.3))
b = int(50 * (ratio / 0.3))
elif ratio < 0.6:
sub_ratio = (ratio - 0.3) / 0.3
r = int(255 - 100 * sub_ratio)
g = int(200 - 80 * sub_ratio)
b = int(100 + 80 * sub_ratio)
else:
sub_ratio = (ratio - 0.6) / 0.4
r = int(155 - 100 * sub_ratio)
g = int(120 - 70 * sub_ratio)
b = int(180 + 75 * sub_ratio)
for x in range(width):
h_variation = math.sin(x * 0.005 + y * 0.002) * 0.1
vr = max(0, min(255, int(r * (1 + h_variation))))
vg = max(0, min(255, int(g * (1 + h_variation))))
vb = max(0, min(255, int(b * (1 + h_variation))))
img.putpixel((x, y), (vr, vg, vb))
return img
def create_sunrise_sky(size):
"""Cria céu de nascer do sol"""
width, height = size
img = Image.new('RGB', size, (0, 0, 0))
draw = ImageDraw.Draw(img)
for y in range(height):
ratio = y / height
if ratio < 0.4:
r = 255
g = int(180 + 50 * (ratio / 0.4))
b = int(100 + 100 * (ratio / 0.4))
elif ratio < 0.7:
sub_ratio = (ratio - 0.4) / 0.3
r = int(255 - 80 * sub_ratio)
g = int(230 - 60 * sub_ratio)
b = int(200 + 30 * sub_ratio)
else:
sub_ratio = (ratio - 0.7) / 0.3
r = int(175 - 100 * sub_ratio)
g = int(170 - 70 * sub_ratio)
b = int(230 + 25 * sub_ratio)
draw.line([(0, y), (width, y)], fill=(r, g, b))
return img
def create_golden_hour_sky(size):
"""Cria céu da hora dourada"""
width, height = size
img = Image.new('RGB', size, (0, 0, 0))
draw = ImageDraw.Draw(img)
for y in range(height):
ratio = y / height
if ratio < 0.5:
r = 255
g = int(200 + 55 * (ratio / 0.5))
b = int(100 + 100 * (ratio / 0.5))
else:
sub_ratio = (ratio - 0.5) / 0.5
r = 255
g = int(255 - 100 * sub_ratio)
b = int(200 - 150 * sub_ratio)
brightness = 1.0 + 0.3 * math.sin(ratio * math.pi)
r = min(255, int(r * brightness))
g = min(255, int(g * brightness))
b = min(255, int(b * brightness))
draw.line([(0, y), (width, y)], fill=(r, g, b))
return img
def create_night_sky(size):
"""Cria céu noturno estrelado"""
width, height = size
img = Image.new('RGB', size, (10, 10, 30))
draw = ImageDraw.Draw(img)
# Gradiente do horizonte
for y in range(height):
ratio = y / height
r = int(10 + 20 * ratio)
g = int(10 + 30 * ratio)
b = int(30 + 50 * ratio)
draw.line([(0, y), (width, y)], fill=(r, g, b))
# Estrelas
for _ in range(200):
x = random.randint(0, width)
y = random.randint(0, height//1.5)
brightness = random.choice([0.3, 0.5, 0.7, 0.9, 1.0])
size = 1 + int(brightness * 2)
color = (int(255*brightness), int(255*brightness), int(255*brightness))
draw.ellipse([x-size, y-size, x+size, y+size], fill=color)
return img
def create_northern_lights_sky(size):
"""Cria céu com aurora boreal"""
width, height = size
img = Image.new('RGB', size, (10, 15, 40))
draw = ImageDraw.Draw(img)
for y in range(height):
ratio = y / height
r = int(10 + 15 * ratio)
g = int(15 + 20 * ratio)
b = int(40 + 30 * ratio)
draw.line([(0, y), (width, y)], fill=(r, g, b))
# Efeito de aurora simplificado
aurora_img = Image.new('RGBA', size, (0, 0, 0, 0))
aurora_draw = ImageDraw.Draw(aurora_img)
for y in range(height//4, height//2, 5):
for x in range(0, width, 10):
wave = int(20 * math.sin(x * 0.02 + y * 0.01))
color = random.choice([(0, 255, 150), (100, 0, 255), (0, 200, 255)])
alpha = random.randint(30, 80)
aurora_draw.ellipse([x-15, y+wave-5, x+15, y+wave+5],
fill=(*color, alpha))
img = Image.alpha_composite(img.convert('RGBA'), aurora_img).convert('RGB')
return img
def create_space_sky(size):
"""Cria céu espacial"""
width, height = size
img = Image.new('RGB', size, (0, 0, 0))
draw = ImageDraw.Draw(img)
# Estrelas
for _ in range(300):
x = random.randint(0, width)
y = random.randint(0, height)
brightness = random.uniform(0.3, 1.0)
size_star = 1 + int(brightness * 2)
color = (int(255*brightness), int(255*brightness), int(255*brightness))
draw.ellipse([x-size_star, y-size_star, x+size_star, y+size_star], fill=color)
return img
def create_foggy_sky(size):
"""Cria céu nebuloso"""
width, height = size
img = Image.new('RGB', size, (200, 210, 220))
draw = ImageDraw.Draw(img)
for y in range(height):
ratio = y / height
r = int(220 - 40 * ratio)
g = int(225 - 30 * ratio)
b = int(230 - 20 * ratio)
draw.line([(0, y), (width, y)], fill=(r, g, b))
return img
def create_rainbow_sky(size):
"""Cria céu com arco-íris"""
width, height = size
img = create_sunny_sky(size)
draw = ImageDraw.Draw(img)
# Arco-íris simplificado
rainbow_colors = [(255,0,0), (255,127,0), (255,255,0), (0,255,0),
(0,0,255), (75,0,130), (148,0,211)]
center_x, center_y = width // 2, height * 1.5
for i, color in enumerate(rainbow_colors):
radius = int(height * 0.8 - i * 10)
for angle in np.linspace(-0.3, 0.3, 50):
x = int(center_x + radius * math.sin(angle * math.pi))
y = int(center_y + radius * math.cos(angle * math.pi))
if 0 <= x < width and 0 <= y < height:
draw.point((x, y), fill=color)
return img
def create_dramatic_sky(size):
"""Cria céu dramático"""
width, height = size
img = Image.new('RGB', size, (60, 80, 120))
draw = ImageDraw.Draw(img)
for y in range(height):
ratio = y / height
r = int(80 - 40 * ratio)
g = int(60 + 40 * ratio)
b = int(120 + 60 * ratio)
variation = math.sin(y * 0.01) * 0.1
r = int(r * (1 + variation))
g = int(g * (1 + variation))
b = int(b * (1 + variation))
draw.line([(0, y), (width, y)], fill=(r, g, b))
return img
def create_pastel_sky(size):
"""Cria céu pastel suave"""
width, height = size
img = Image.new('RGB', size, (0, 0, 0))
draw = ImageDraw.Draw(img)
pastel_colors = [(255, 200, 220), (220, 230, 255),
(220, 255, 220), (255, 240, 200)]
for y in range(height):
ratio = y / height
color_idx = ratio * (len(pastel_colors) - 1)
idx1 = int(color_idx)
idx2 = min(idx1 + 1, len(pastel_colors) - 1)
blend = color_idx - idx1
r1, g1, b1 = pastel_colors[idx1]
r2, g2, b2 = pastel_colors[idx2]
r = int(r1 * (1 - blend) + r2 * blend)
g = int(g1 * (1 - blend) + g2 * blend)
b = int(b1 * (1 - blend) + b2 * blend)
draw.line([(0, y), (width, y)], fill=(r, g, b))
return img
def create_gradient_sky(size):
"""Cria céu com gradiente"""
width, height = size
img = Image.new('RGB', size, (0, 0, 0))
draw = ImageDraw.Draw(img)
# Gradiente azul para roxo
for y in range(height):
ratio = y / height
r = int(100 + 155 * ratio)
g = int(100 + 100 * ratio)
b = int(255 - 100 * ratio)
draw.line([(0, y), (width, y)], fill=(r, g, b))
return img
def add_simple_clouds(base_image, count=5, cloud_type="cumulus"):
"""Adiciona nuvens simples à imagem"""
img = base_image.copy()
width, height = img.size
if img.mode != 'RGBA':
img = img.convert('RGBA')
cloud_img = Image.new('RGBA', (width, height), (0, 0, 0, 0))
cloud_draw = ImageDraw.Draw(cloud_img)
for _ in range(count):
cloud_x = random.randint(0, width)
cloud_y = random.randint(0, height // 2)
cloud_size = random.randint(width // 8, width // 4)
if cloud_type == "cumulus":
# Nuvens redondas
cloud_color = (255, 255, 255, 180)
cloud_draw.ellipse([cloud_x-cloud_size, cloud_y-cloud_size//3,
cloud_x+cloud_size, cloud_y+cloud_size//3],
fill=cloud_color)
else:
# Nuvens alongadas
cloud_color = (240, 240, 250, 150)
for i in range(3):
offset_x = random.randint(-cloud_size//3, cloud_size//3)
offset_y = random.randint(-cloud_size//6, cloud_size//6)
ellipse_size = cloud_size * random.uniform(0.3, 0.6)
cloud_draw.ellipse([cloud_x+offset_x-ellipse_size,
cloud_y+offset_y-ellipse_size//2,
cloud_x+offset_x+ellipse_size,
cloud_y+offset_y+ellipse_size//2],
fill=cloud_color)
# Aplicar suavização
cloud_img = cloud_img.filter(ImageFilter.GaussianBlur(radius=3))
img = Image.alpha_composite(img, cloud_img)
return img.convert('RGB')
# ========== FUNÇÃO PRINCIPAL ==========
def replace_sky(image, sky_type, intensity=0.8, auto_detect=True):
"""Substitui o céu na imagem"""
try:
log_message(f"Substituindo céu - Tipo: {sky_type}, Intensidade: {intensity}")
# Validar imagem
img, msg = validate_image(image)
if img is None:
return None, f"❌ {msg}", None
# Redimensionar se necessário
img = resize_image(img, MAX_IMAGE_SIZE)
width, height = img.size
# Detectar céu
if auto_detect:
sky_mask = detect_sky_region(img)
log_message("✅ Detecção automática concluída")
else:
sky_mask = np.zeros((height, width), dtype=np.uint8)
sky_mask[:int(height*0.4), :] = 255
log_message("⚠️ Usando máscara padrão")
# Criar novo céu
sky_generators = {
"sunny_day": create_sunny_sky,
"blue_sky": create_blue_sky,
"cloudy": create_cloudy_sky,
"stormy": create_stormy_sky,
"sunset": create_sunset_sky,
"sunrise": create_sunrise_sky,
"golden_hour": create_golden_hour_sky,
"night_sky": create_night_sky,
"northern_lights": create_northern_lights_sky,
"space": create_space_sky,
"foggy": create_foggy_sky,
"rainbow": create_rainbow_sky,
"dramatic": create_dramatic_sky,
"pastel": create_pastel_sky,
"gradient": create_gradient_sky
}
generator = sky_generators.get(sky_type, create_sunny_sky)
new_sky = generator((width, height))
# Converter máscara
mask_pil = Image.fromarray(sky_mask).convert('L')
mask_pil = mask_pil.filter(ImageFilter.GaussianBlur(radius=5))
# Ajustar intensidade
if intensity != 1.0:
mask_array = np.array(mask_pil).astype(float)
mask_array = mask_array * intensity
mask_pil = Image.fromarray(np.clip(mask_array, 0, 255).astype(np.uint8))
# Aplicar substituição
result = img.copy()
result.paste(new_sky, (0, 0), mask_pil)
# Ajustar cores para integração
if intensity > 0.5:
# Suavizar transição
edges = mask_pil.filter(ImageFilter.FIND_EDGES())
edges = edges.point(lambda x: 255 if x > 30 else 0)
if np.any(np.array(edges) > 0):
blurred_result = result.filter(ImageFilter.GaussianBlur(radius=2))
transition_mask = edges.filter(ImageFilter.GaussianBlur(radius=3))
transition_mask = transition_mask.point(lambda x: x // 2)
result = Image.composite(blurred_result, result, transition_mask)
log_message("✅ Céu substituído com sucesso")
# Máscara para visualização
mask_display = Image.fromarray(sky_mask).convert('RGB')
mask_display = mask_display.resize((width//2, height//2))
status_msg = f"✅ Céu '{SKY_LIBRARY.get(sky_type, sky_type)}' aplicado!"
return result, status_msg, mask_display
except Exception as e:
error_msg = f"❌ Erro ao substituir céu: {str(e)}"
log_message(error_msg)
import traceback
traceback.print_exc()
return None, error_msg, None
def save_image(image, filename="sky_replaced.png"):
"""Salva imagem em arquivo temporário"""
try:
if image is None:
return None
temp_file = tempfile.NamedTemporaryFile(delete=False, suffix=".png")
image.save(temp_file.name, "PNG", optimize=True)
file_size = os.path.getsize(temp_file.name) // 1024
log_message(f"Imagem salva: {temp_file.name} ({file_size}KB)")
return temp_file.name
except Exception as e:
log_message(f"❌ Erro ao salvar: {str(e)}")
return None
# ========== INTERFACE GRADIO ==========
print("🌤️ Criando interface Sky Replacer...")
custom_css = """
.gradio-container {
max-width: 1400px !important;
margin: 0 auto !important;
}
.header {
text-align: center;
margin-bottom: 30px;
padding: 25px;
background: linear-gradient(135deg, #36D1DC 0%, #5B86E5 100%);
border-radius: 20px;
color: white;
}
.header h1 {
margin: 0;
font-size: 2.8em;
font-weight: 700;
}
.header p {
margin: 10px 0 0 0;
font-size: 1.2em;
opacity: 0.9;
}
.sky-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
gap: 15px;
margin: 20px 0;
}
.sky-card {
background: white;
border-radius: 15px;
padding: 20px;
text-align: center;
cursor: pointer;
transition: all 0.3s ease;
border: 2px solid transparent;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
.sky-card:hover {
transform: translateY(-5px);
box-shadow: 0 8px 15px rgba(54, 209, 220, 0.3);
border-color: #36D1DC;
}
.sky-card.active {
border-color: #5B86E5;
background: #f0f9ff;
}
.sky-icon {
font-size: 2.5em;
margin-bottom: 10px;
}
.sky-name {
font-weight: 600;
color: #333;
}
.image-container {
border-radius: 15px;
overflow: hidden;
border: 2px solid #e0e0e0;
}
.status-box {
padding: 15px;
border-radius: 10px;
margin: 15px 0;
font-weight: 500;
}
.status-success {
background: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}
.status-error {
background: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}
.download-btn {
background: linear-gradient(135deg, #36D1DC 0%, #5B86E5 100%);
color: white;
border: none;
padding: 12px 30px;
border-radius: 25px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
width: 100%;
}
.download-btn:hover {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(54, 209, 220, 0.4);
}
.footer {
text-align: center;
margin-top: 30px;
padding: 20px;
color: #666;
font-size: 0.9em;
}
"""
# Interface principal
def create_interface():
with gr.Blocks(title="🌤️ Sky Replacer - Photoshop AI App 10",
theme=gr.themes.Soft(),
css=custom_css) as demo:
# Header
gr.HTML(f"""
<div class="header">
<h1>🌤️ Sky Replacer</h1>
<p>App 10 do Photoshop AI Ecosystem - Substituição profissional de céus</p>
</div>
""")
with gr.Row():
# Coluna esquerda - Upload e Controles
with gr.Column(scale=1):
with gr.Group():
gr.Markdown("### 📤 Upload da Imagem")
image_input = gr.Image(
label="Arraste uma foto com céu",
type="pil",
height=300,
elem_classes="image-container"
)
with gr.Group():
gr.Markdown("### ⚙️ Configurações")
sky_select = gr.Dropdown(
choices=list(SKY_LIBRARY.values()),
value="☀️ Dia Ensolarado",
label="Tipo de Céu",
interactive=True
)
intensity_slider = gr.Slider(
minimum=0.1,
maximum=1.0,
value=0.8,
step=0.1,
label="Intensidade da Substituição",
interactive=True
)
auto_detect = gr.Checkbox(
label="Detecção automática de céu",
value=True,
interactive=True
)
apply_button = gr.Button(
"🌤️ Aplicar Novo Céu",
variant="primary",
size="lg"
)
# Coluna central - Biblioteca de Céus
with gr.Column(scale=2):
with gr.Group():
gr.Markdown("### 🌈 Biblioteca de Céus")
# Criar grid de céus
sky_html = "<div class='sky-grid'>"
for sky_key, sky_name in SKY_LIBRARY.items():
emoji = sky_name.split()[0]
sky_html += f"""
<div class='sky-card' onclick="selectSky('{sky_key}')">
<div class='sky-icon'>{emoji}</div>
<div class='sky-name'>{sky_name}</div>
</div>
"""
sky_html += "</div>"
gr.HTML(sky_html)
# Dropdown oculto para controle
sky_select_hidden = gr.Dropdown(
choices=list(SKY_LIBRARY.keys()),
value="sunny_day",
label="Céu Selecionado",
visible=False
)
# Coluna direita - Resultados
with gr.Column(scale=1):
with gr.Tabs():
with gr.TabItem("🎨 Resultado"):
image_output = gr.Image(
label="Imagem com Céu Substituído",
type="pil",
height=300,
elem_classes="image-container"
)
with gr.TabItem("🎯 Máscara"):
mask_output = gr.Image(
label="Área do Céu Detectada",
type="pil",
height=300,
elem_classes="image-container",
visible=False
)
with gr.Group():
gr.Markdown("### 📊 Status")
status_output = gr.HTML(
value="<div class='status-box status-success'>Pronto para substituir céus!</div>",
label="Status"
)
with gr.Group():
gr.Markdown("### 💾 Download")
download_button = gr.Button(
"⬇️ Baixar Imagem",
variant="secondary",
size="lg",
elem_classes="download-btn"
)
download_file = gr.File(
label="Arquivo para download",
visible=False
)
# Instruções
gr.HTML("""
<div style="background: #f8f9fa; padding: 20px; border-radius: 15px; margin-top: 20px;">
<h3>📋 Como Usar:</h3>
<ol style="margin: 10px 0; padding-left: 20px;">
<li><strong>Faça upload</strong> de uma imagem com céu visível</li>
<li><strong>Escolha um céu</strong> da biblioteca clicando nos cards</li>
<li><strong>Ajuste a intensidade</strong> para controlar a transição</li>
<li><strong>Clique em "Aplicar Novo Céu"</strong> para processar</li>
<li><strong>Baixe o resultado</strong> ou experimente outros céus</li>
</ol>
<p style="margin-top: 10px; color: #666;">
💡 <strong>Dica:</strong> Use intensidade mais baixa (0.3-0.5) para transições mais sutis
</p>
</div>
""")
# Footer
gr.HTML("""
<div class="footer">
<p>🌤️ Photoshop AI Ecosystem - App 10: Sky Replacer</p>
<p>Substituição profissional de céus com detecção inteligente</p>
</div>
""")
# JavaScript para interatividade
gr.HTML("""
<script>
function selectSky(skyKey) {
// Atualizar dropdown visível
const skyNames = {
"sunny_day": "☀️ Dia Ensolarado",
"blue_sky": "🔵 Céu Azul Limpo",
"cloudy": "☁️ Céu Nublado",
"stormy": "⛈️ Tempestade",
"sunset": "🌅 Pôr do Sol",
"sunrise": "🌇 Nascer do Sol",
"golden_hour": "✨ Hora Dourada",
"night_sky": "🌙 Noite Estrelada",
"northern_lights": "🌈 Aurora Boreal",
"space": "🚀 Espaço Sideral",
"foggy": "🌫️ Nebuloso",
"rainbow": "🌈 Arco-Íris",
"dramatic": "🎭 Dramático",
"pastel": "🎨 Pastel",
"gradient": "🔄 Gradiente"
};
document.querySelectorAll('[data-testid="dropdown"]')[0].value = skyNames[skyKey];
// Atualizar seleção visual
document.querySelectorAll('.sky-card').forEach(card => {
card.classList.remove('active');
});
// Destacar card selecionado
const selectedCard = document.querySelector(`[onclick="selectSky('${skyKey}')"]`);
if (selectedCard) {
selectedCard.classList.add('active');
}
// Disparar evento de mudança
const event = new Event('change');
document.querySelectorAll('[data-testid="dropdown"]')[0].dispatchEvent(event);
}
// Inicializar com primeiro céu ativo
document.addEventListener('DOMContentLoaded', function() {
setTimeout(() => {
selectSky('sunny_day');
}, 100);
});
</script>
""")
# Funções de processamento
def process_sky_replacement(image, sky_label, intensity, auto_detect_flag):
"""Processa a substituição do céu"""
if image is None:
return None, "<div class='status-box status-error'>❌ Por favor, faça upload de uma imagem.</div>", gr.update(visible=False)
# Converter label para key
sky_key = None
for key, value in SKY_LIBRARY.items():
if value == sky_label:
sky_key = key
break
if sky_key is None:
return None, f"<div class='status-box status-error'>❌ Tipo de céu inválido: {sky_label}</div>", gr.update(visible=False)
result, message, mask_display = replace_sky(
image, sky_key, intensity, auto_detect_flag
)
if result is None:
return None, f"<div class='status-box status-error'>{message}</div>", gr.update(visible=False)
status_html = f"<div class='status-box status-success'>{message}</div>"
# Mostrar máscara
mask_visible = gr.update(visible=mask_display is not None)
return result, status_html, mask_display, mask_visible
def trigger_download(image):
"""Dispara o download da imagem"""
if image is None:
return None
temp_file = save_image(image)
return temp_file
# Conectar eventos
apply_button.click(
fn=process_sky_replacement,
inputs=[image_input, sky_select, intensity_slider, auto_detect],
outputs=[image_output, status_output, mask_output, mask_output]
)
# Atualizar automaticamente quando parâmetros mudam
sky_select.change(
fn=process_sky_replacement,
inputs=[image_input, sky_select, intensity_slider, auto_detect],
outputs=[image_output, status_output, mask_output, mask_output]
)
intensity_slider.change(
fn=process_sky_replacement,
inputs=[image_input, sky_select, intensity_slider, auto_detect],
outputs=[image_output, status_output, mask_output, mask_output]
)
auto_detect.change(
fn=process_sky_replacement,
inputs=[image_input, sky_select, intensity_slider, auto_detect],
outputs=[image_output, status_output, mask_output, mask_output]
)
# Botão de download
download_button.click(
fn=trigger_download,
inputs=[image_output],
outputs=[download_file]
)
return demo
# ========== EXECUÇÃO PRINCIPAL ==========
if __name__ == "__main__":
print("🚀 Inicializando Sky Replacer...")
# Verificar se está no Hugging Face
IS_HUGGINGFACE = os.getenv('SPACE_ID') is not None
# Criar interface
demo = create_interface()
# Configurações
server_name = "0.0.0.0" if IS_HUGGINGFACE else "127.0.0.1"
server_port = int(os.getenv('PORT', 7860))
print(f"🌐 Servidor: {server_name}:{server_port}")
print(f"🌤️ Céus disponíveis: {len(SKY_LIBRARY)}")
print("🚀 Sky Replacer pronto para uso!")
# Iniciar servidor
try:
demo.launch(
server_name=server_name,
server_port=server_port,
share=False,
debug=False,
show_error=True,
quiet=True
)
except Exception as e:
print(f"❌ Erro ao iniciar servidor: {str(e)}")
print("⚠️ Tentando configuração alternativa...")
# Configuração alternativa
demo.launch(
server_name="0.0.0.0",
server_port=7860,
share=False,
quiet=True
)