hdr-creator / app.py
Daniel251's picture
Update app.py
5bccc00 verified
Raw
History Blame Contribute Delete
25.3 kB
"""
🌈 HDR Creator Pro - Ultimate Version
Cria imagens HDR com assinaturas visuais extremas e comparação lado a lado.
Autor: Daniel251
Versão: 1.2.0
Data: 2026
"""
import gradio as gr
from PIL import Image, ImageEnhance, ImageFilter, ImageOps
import numpy as np
import cv2
import tempfile
import time
import os
import warnings
import traceback
warnings.filterwarnings("ignore")
# ========== CONFIGURAÇÃO ==========
MAX_IMAGE_SIZE = 1200
HDR_MODES = {
"standard_hdr": "🌈 HDR Padrão (Equilibrado)",
"natural_hdr": "🌿 HDR Natural (Fiel à Realidade)",
"dramatic_hdr": "🎭 HDR Dramático (Alto Contraste/Cinema)",
"artistic_hdr": "🎨 HDR Artístico (Efeito Orton/Sonho)",
"vibrant_hdr": "💥 HDR Vibrante (Explosão de Cores)",
"monochrome_hdr": "⚫ HDR Noir (P&B Profundo)",
"infrared_hdr": "🔴 HDR Surreal (Infravermelho Falso)",
"cyberpunk_hdr": "🌆 HDR Cyberpunk (Neon/Noite)",
"vintage_hdr": "🎞️ HDR Vintage (Filme Antigo)",
"single_image": "📷 HDR de Uma Imagem"
}
# ========== FUNÇÕES DE LOG ==========
def log_event(level, message, context="SYSTEM"):
timestamp = time.strftime("%Y-%m-%d %H:%M:%S")
print(f"[{timestamp}] [{level}] [{context}] {message}")
# ========== PROCESSAMENTO CORE ==========
def validate_and_resize(image_path):
try:
img = Image.open(image_path).convert('RGB')
if max(img.size) > MAX_IMAGE_SIZE:
ratio = MAX_IMAGE_SIZE / max(img.size)
new_size = (int(img.width * ratio), int(img.height * ratio))
img = img.resize(new_size, Image.Resampling.LANCZOS)
return img
except Exception as e:
log_event("ERROR", f"Erro ao carregar imagem: {str(e)}")
return None
def align_images_cv2(images):
if len(images) < 2: return images
log_event("INFO", "Alinhando múltiplas exposições...", "ALIGN")
img_arrays = [np.array(img) for img in images]
ref_gray = cv2.cvtColor(img_arrays[0], cv2.COLOR_RGB2GRAY)
aligned = [images[0]]
for i in range(1, len(img_arrays)):
try:
warp_matrix = np.eye(2, 3, dtype=np.float32)
_, warp_matrix = cv2.findTransformECC(
ref_gray,
cv2.cvtColor(img_arrays[i], cv2.COLOR_RGB2GRAY),
warp_matrix,
cv2.MOTION_AFFINE
)
w_img = cv2.warpAffine(
img_arrays[i],
warp_matrix,
(img_arrays[0].shape[1], img_arrays[0].shape[0]),
flags=cv2.INTER_LINEAR + cv2.WARP_INVERSE_MAP
)
aligned.append(Image.fromarray(w_img.astype(np.uint8)))
except:
aligned.append(images[i])
return aligned
# ========== TONEMAPPING AVANÇADO (ESTILO PHOTOSHOP) ==========
def apply_tonemap(hdr_array, mode, strength):
"""Aplica tonemapping avançado baseado no modo selecionado"""
# Normalizar para 0-1
hdr_norm = hdr_array.astype(float) / 255.0
if mode == "standard_hdr":
# Curva S suave - padrão Photoshop
gamma = 1.0 / (0.7 + strength * 0.3)
result = np.power(hdr_norm, gamma)
# Pequeno aumento de contraste
result = (result - 0.5) * 1.1 + 0.5
elif mode == "natural_hdr":
# Preserva tons médios, comprime altas luzes
gamma = 1.0 / (0.9 + strength * 0.1)
result = np.power(hdr_norm, gamma)
# Curva mais linear
mask = result > 0.5
result[mask] = 0.5 + (result[mask] - 0.5) * 0.8
elif mode == "dramatic_hdr":
# Curva agressiva - tons escuros mais escuros, claros preservados
gamma = 1.0 / (0.5 + strength * 0.5)
result = np.power(hdr_norm, gamma)
# Aumento significativo de contraste
result = (result - 0.5) * 1.8 + 0.5
elif mode == "artistic_hdr":
# Comprime dinâmica para criar efeito dreamy
gamma = 1.0 / (0.8 + strength * 0.2)
result = np.power(hdr_norm, gamma)
# Achatamento de contraste
result = (result - 0.5) * 0.7 + 0.5
elif mode == "vibrant_hdr":
# Curva que aumenta saturação nos tons médios
gamma = 1.0 / (0.6 + strength * 0.4)
result = np.power(hdr_norm, gamma)
# Leve aumento de contraste
result = (result - 0.5) * 1.2 + 0.5
else:
# Padrão para outros modos
gamma = 1.0 / (0.8 + strength * 0.2)
result = np.power(hdr_norm, gamma)
return np.clip(result * 255, 0, 255).astype(np.uint8)
def apply_base_hdr(images, strength, mode):
"""Processa HDR base com alinhamento e tonemapping específico"""
if len(images) == 1:
# Para imagem única, simula múltiplas exposições
img_array = np.array(images[0]).astype(float)
# Gera 3 exposições virtuais
under = np.clip(img_array * 0.3, 0, 255)
normal = img_array
over = np.clip(img_array * 2.0, 0, 255)
# Combinação ponderada
hdr = under * 0.3 + normal * 0.4 + over * 0.3
else:
# Para múltiplas imagens
aligned = align_images_cv2(images)
arrs = [np.array(img).astype(float) for img in aligned]
# Combinação baseada no modo
if mode in ["dramatic_hdr", "monochrome_hdr"]:
hdr = np.max(arrs, axis=0) # Preserva detalhes nas sombras
elif mode == "natural_hdr":
hdr = np.median(arrs, axis=0) # Reduz ruído
else:
# Média ponderada com ênfase nas exposições médias
weights = [0.3, 0.4, 0.3] if len(arrs) == 3 else [1.0/len(arrs)] * len(arrs)
hdr = np.sum([arr * w for arr, w in zip(arrs, weights[:len(arrs)])], axis=0)
# Aplica tonemapping específico
return apply_tonemap(hdr, mode, strength)
# ========== ASSINATURAS VISUAIS ESPECÍFICAS ==========
def create_standard(images, s):
"""HDR Padrão - Equilibrado como Photoshop"""
base = apply_base_hdr(images, s, "standard_hdr")
img = Image.fromarray(base)
# Ajustes finais
img = ImageEnhance.Contrast(img).enhance(1.15)
img = ImageEnhance.Color(img).enhance(1.05)
img = ImageEnhance.Sharpness(img).enhance(1.1)
return img, "HDR Padrão: Equilíbrio tonal perfeito, detalhes naturais preservados."
def create_natural(images, s):
"""HDR Natural - Minimalista e realista"""
base = apply_base_hdr(images, s * 0.7, "natural_hdr") # Menor intensidade
img = Image.fromarray(base)
# Ajustes sutis
img = ImageEnhance.Color(img).enhance(0.9)
img = ImageEnhance.Contrast(img).enhance(1.05)
img = ImageEnhance.Sharpness(img).enhance(1.05)
# Redução de ruído (suave)
img = img.filter(ImageFilter.MedianFilter(size=1))
return img, "HDR Natural: Cores orgânicas, dinâmica preservada, sem artificialidade."
def create_dramatic(images, s):
"""HDR Dramático - Alto contraste, estilo cinematográfico"""
base = apply_base_hdr(images, s * 1.3, "dramatic_hdr")
img = Image.fromarray(base)
# Ajustes agressivos
img = ImageEnhance.Contrast(img).enhance(1.8 + s * 0.5)
img = ImageEnhance.Color(img).enhance(0.85) # Dessaturação leve para drama
img = ImageEnhance.Sharpness(img).enhance(1.3)
# Máscara de nitidez para realçar bordas
img = img.filter(ImageFilter.UnsharpMask(radius=2, percent=180, threshold=3))
# Aumento seletivo de brilho nas altas luzes
enhancer = ImageEnhance.Brightness(img)
img = enhancer.enhance(1.05)
return img, "HDR Dramático: Contraste cinematográfico, sombras profundas, texturas realçadas."
def create_artistic(images, s):
"""HDR Artístico - Efeito Orton/Sonho"""
base = apply_base_hdr(images, s * 0.8, "artistic_hdr")
img = Image.fromarray(base)
# Efeito Orton: sobreposição de blur
blur_amount = int(5 + s * 10)
blurred = img.filter(ImageFilter.GaussianBlur(radius=blur_amount))
# Mistura para efeito dreamy
blend_amount = 0.3 + s * 0.3
img = Image.blend(img, blurred, blend_amount)
# Ajustes de cor
img = ImageEnhance.Brightness(img).enhance(1.1)
img = ImageEnhance.Color(img).enhance(1.25)
img = ImageEnhance.Contrast(img).enhance(0.9) # Contraste reduzido
return img, "HDR Artístico: Efeito Orton etéreo, atmosfera onírica, cores suavizadas."
def create_vibrant(images, s):
"""HDR Vibrante - Cores saturadas e vivas"""
base = apply_base_hdr(images, s, "vibrant_hdr")
img = Image.fromarray(base)
# Aumento agressivo de saturação
img = ImageEnhance.Color(img).enhance(2.0 + s * 1.0)
# Contraste moderado
img = ImageEnhance.Contrast(img).enhance(1.25)
# Brilho ajustado
img = ImageEnhance.Brightness(img).enhance(1.05)
# Nitidez para realçar detalhes
img = ImageEnhance.Sharpness(img).enhance(1.2)
return img, "HDR Vibrante: Saturação máxima, cores intensas, energia visual explosiva."
def create_monochrome(images, s):
"""HDR Noir - P&B profundo com textura"""
base = apply_base_hdr(images, s * 1.2, "monochrome_hdr")
img = Image.fromarray(base)
# Conversão para P&B com canal de luminância
img_gray = ImageOps.grayscale(img).convert("RGB")
# Contraste extremo
img = ImageEnhance.Contrast(img_gray).enhance(2.0 + s * 0.5)
# Brilho ajustado
img = ImageEnhance.Brightness(img).enhance(0.9)
# Textura adicionada
arr = np.array(img).astype(float)
noise = np.random.normal(0, 8 * s, arr.shape)
arr = np.clip(arr + noise, 0, 255).astype(np.uint8)
img = Image.fromarray(arr)
return img, "HDR Noir: Preto e branco profundo, contraste dramático, textura cinematográfica."
def create_infrared(images, s):
"""HDR Surreal - Efeito infravermelho falso"""
img, _ = create_standard(images, s * 0.7)
# Separa canais
r, g, b = img.split()
# Efeito infravermelho: troca de canais
# Canal vermelho recebe verde (folhagens ficam claras)
new_r = g.point(lambda x: min(x * 1.5, 255))
# Canal azul escurecido
new_b = b.point(lambda x: x * 0.7)
# Canal verde com vermelho
new_g = r.point(lambda x: x * 0.8)
img = Image.merge("RGB", (new_r, new_g, new_b))
# Ajustes finais
img = ImageEnhance.Contrast(img).enhance(1.3)
img = ImageEnhance.Color(img).enhance(1.2)
return img, "HDR Surreal: Espectro infravermelho simulado, folhagens brancas, céu escuro."
def create_cyberpunk(images, s):
"""HDR Cyberpunk - Estética neon futurista"""
img, _ = create_standard(images, s)
# Separa canais para ajustes individuais
r, g, b = img.split()
# Realça azuis e vermelhos (neon)
new_r = ImageEnhance.Brightness(r).enhance(1.6 + s * 0.5)
new_b = ImageEnhance.Brightness(b).enhance(1.8 + s * 0.7)
new_g = ImageEnhance.Brightness(g).enhance(0.8) # Verdes reduzidos
img = Image.merge("RGB", (new_r, new_g, new_b))
# Ajustes gerais
img = ImageEnhance.Brightness(img).enhance(0.85) # Escurece
img = ImageEnhance.Contrast(img).enhance(1.4)
img = ImageEnhance.Color(img).enhance(1.5)
# Adiciona leve vinheta
width, height = img.size
arr = np.array(img).astype(float)
# Cria máscara de vinheta
x = np.linspace(-1, 1, width)
y = np.linspace(-1, 1, height)
X, Y = np.meshgrid(x, y)
vignette = 1 - np.sqrt(X**2 + Y**2) * 0.3
vignette = vignette[:, :, np.newaxis]
arr = arr * vignette
img = Image.fromarray(np.clip(arr, 0, 255).astype(np.uint8))
return img, "HDR Cyberpunk: Estética Neon-Noir futurista, cores sintéticas, atmosfera noturna."
def create_vintage(images, s):
"""HDR Vintage - Filme antigo com grão"""
img, _ = create_standard(images, s * 0.6)
# Dessaturação e tom sépia
img = ImageEnhance.Color(img).enhance(0.65)
# Aplica sépia
sepia_base = ImageOps.colorize(
ImageOps.grayscale(img),
"#3d2b1f", # Sombra
"#fff5e6", # Destaque
"#8b6b42" # Meio-tom
)
img = Image.blend(img, sepia_base, 0.7)
# Adiciona grão de filme
arr = np.array(img).astype(float)
grain = np.random.normal(0, 12 * s, arr.shape)
arr = np.clip(arr + grain, 0, 255)
# Adiciona leve vinheta
height, width = arr.shape[0], arr.shape[1]
x = np.linspace(-1, 1, width)
y = np.linspace(-1, 1, height)
X, Y = np.meshgrid(x, y)
vignette = 1 - np.sqrt(X**2 + Y**2) * 0.4
vignette = vignette[:, :, np.newaxis]
arr = arr * vignette
img = Image.fromarray(np.clip(arr, 0, 255).astype(np.uint8))
# Contraste suave
img = ImageEnhance.Contrast(img).enhance(1.1)
return img, "HDR Vintage: Visual de filme analógico desgastado, grão natural, tons sépia."
def create_single(image, s):
"""HDR de imagem única - Recuperação de detalhes"""
# Converte para array
img_arr = np.array(image).astype(float)
# Cria múltiplas exposições virtuais
exp1 = np.clip(img_arr * 0.25, 0, 255) # Subexposta
exp2 = img_arr # Normal
exp3 = np.clip(img_arr * 2.2, 0, 255) # Superexposta
# Mapeamento de tons local (similar ao Photoshop)
# Combina detalhes das sombras (exp1), tons médios (exp2) e altas luzes (exp3)
# Máscara para sombras
luminance = 0.299 * exp2[:,:,0] + 0.587 * exp2[:,:,1] + 0.114 * exp2[:,:,2]
shadow_mask = np.clip((0.5 - luminance) * 2, 0, 1)[:,:,np.newaxis]
# Máscara para altas luzes
highlight_mask = np.clip((luminance - 0.7) * 3, 0, 1)[:,:,np.newaxis]
# Máscara para tons médios
midtone_mask = 1 - shadow_mask - highlight_mask
midtone_mask = np.clip(midtone_mask, 0, 1)
# Combinação ponderada
hdr = exp1 * shadow_mask + exp2 * midtone_mask + exp3 * highlight_mask
# Ajuste de contraste local
result = np.clip(hdr, 0, 255).astype(np.uint8)
img = Image.fromarray(result)
# Ajustes finais
img = ImageEnhance.Contrast(img).enhance(1.3 + s * 0.3)
img = ImageEnhance.Sharpness(img).enhance(1.2)
img = ImageEnhance.Color(img).enhance(1.1)
return img, "HDR Simulado: Detalhes recuperados de uma única foto, dinâmica expandida."
# ========== INTERFACE GRADIO ==========
def create_interface():
custom_css = """
.gradio-container { max-width: 1400px !important; margin: 0 auto !important; background-color: #0f172a; color: #f8fafc; }
.header { text-align: center; padding: 30px; background: linear-gradient(135deg, #1e293b 0%, #0f172a 100%); border-bottom: 2px solid #334155; margin-bottom: 20px; }
.header h1 { font-size: 3em; margin: 0; background: linear-gradient(to right, #38bdf8, #818cf8); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
.header p { color: #94a3b8; font-size: 1.2em; margin-top: 10px; }
.status-box { padding: 15px; border-radius: 8px; text-align: center; margin: 15px 0; font-weight: bold; font-size: 1.1em; }
.status-success { background: #065f46; color: #34d399; border: 1px solid #059669; }
.status-info { background: #1e3a8a; color: #93c5fd; border: 1px solid #2563eb; }
.status-warning { background: #7c2d12; color: #fdba74; border: 1px solid #ea580c; }
.status-error { background: #7f1d1d; color: #fca5a5; border: 1px solid #dc2626; }
.image-container { border: 2px solid #334155; border-radius: 12px; overflow: hidden; background: #1e293b; padding: 10px; }
.image-label { font-weight: bold; color: #cbd5e1; margin-bottom: 5px; text-align: center; }
button#component-4 { background: linear-gradient(135deg, #4f46e5 0%, #7c3aed 100%) !important; }
button#component-4:hover { background: linear-gradient(135deg, #4338ca 0%, #6d28d9 100%) !important; }
.slider-container { padding: 15px; background: #1e293b; border-radius: 8px; margin: 10px 0; }
.output-image { max-width: 100% !important; height: auto !important; }
"""
with gr.Blocks(css=custom_css, title="HDR Creator Pro Ultra") as demo:
gr.HTML("""
<div class='header'>
<h1>🌈 HDR Creator Pro Ultra</h1>
<p>Motor de Renderização HDR com Assinaturas Visuais de Alta Precisão | Versão 1.2.0</p>
<p style="font-size: 0.9em; color: #64748b;">
Para HDR verdadeiro: Carregue 2+ imagens com diferentes exposições<br>
Para HDR de imagem única: Carregue 1 imagem e selecione "Single Image"
</p>
</div>
""")
with gr.Row():
with gr.Column(scale=1):
with gr.Group():
gr.Markdown("### 📤 Entrada de Dados")
image_input = gr.File(
label="Upload de Imagens",
file_count="multiple",
type="filepath",
file_types=["image"],
elem_id="file-upload"
)
hdr_select = gr.Dropdown(
choices=list(HDR_MODES.values()),
value=HDR_MODES["standard_hdr"],
label="Estilo HDR",
info="Selecione o estilo de processamento"
)
gr.Markdown("### ⚙️ Configurações")
with gr.Row():
strength_slider = gr.Slider(
0.1, 2.0, 1.0, step=0.1,
label="Intensidade do Efeito",
info="Controla a força do processamento HDR"
)
apply_button = gr.Button(
"🚀 RENDERIZAR HDR",
variant="primary",
size="lg",
scale=1
)
with gr.Group():
gr.Markdown("### 📊 Status do Processamento")
status_output = gr.HTML(
"<div class='status-box status-info'>Pronto para processar. Carregue imagens e clique em Renderizar.</div>"
)
gr.Markdown("### 📥 Exportação")
download_file = gr.File(
label="Baixar Imagem HDR Final",
file_types=[".png"]
)
with gr.Column(scale=2):
gr.Markdown("### 🔍 Comparação em Tempo Real")
with gr.Row():
with gr.Column():
gr.Markdown("**Original (Primeira Imagem)**")
original_preview = gr.Image(
label="Original",
type="pil",
interactive=False,
height=450,
elem_classes="output-image"
)
with gr.Column():
gr.Markdown("**Resultado HDR Final**")
hdr_preview = gr.Image(
label="HDR Result",
type="pil",
interactive=False,
height=450,
elem_classes="output-image"
)
gr.Markdown("### ℹ️ Guia Rápido")
with gr.Accordion("Clique para expandir", open=False):
gr.Markdown("""
**Modos HDR:**
- **Padrão**: Equilíbrio perfeito entre contraste e cores
- **Natural**: Minimalista, preserva aparência original
- **Dramático**: Contraste cinematográfico agressivo
- **Artístico**: Efeito Orton etéreo e dreamy
- **Vibrante**: Cores saturadas e vivas
- **Noir**: P&B profundo com textura
- **Surreal**: Efeito infravermelho
- **Cyberpunk**: Estética neon futurista
- **Vintage**: Filme antigo com grão
- **Single Image**: HDR de uma única foto
**Dicas:**
- Use 3+ imagens com diferentes exposições para melhor qualidade
- Ajuste a intensidade conforme necessário (comece com 1.0)
- Para paisagens: HDR Natural ou Artístico
- Para arquitetura: HDR Dramático
- Para retratos: HDR Padrão com intensidade baixa
""")
def process(files, label, s):
if not files:
return (
None,
None,
"<div class='status-box status-error'>❌ Erro: Nenhuma imagem selecionada.</div>",
None
)
try:
# Identifica o modo selecionado
key = next((k for k, v in HDR_MODES.items() if v == label), "standard_hdr")
log_event("INFO", f"Iniciando processamento: {key} com {len(files)} imagens")
# Carrega e valida imagens
imgs = []
for f in files:
img = validate_and_resize(f.name)
if img is not None:
imgs.append(img)
else:
log_event("WARNING", f"Falha ao carregar imagem: {f.name}")
if not imgs:
return (
None,
None,
"<div class='status-box status-error'>❌ Erro: Falha ao carregar todas as imagens.</div>",
None
)
orig = imgs[0]
# Mapeamento das funções de processamento
funcs = {
"standard_hdr": create_standard,
"natural_hdr": create_natural,
"dramatic_hdr": create_dramatic,
"artistic_hdr": create_artistic,
"vibrant_hdr": create_vibrant,
"monochrome_hdr": create_monochrome,
"infrared_hdr": create_infrared,
"cyberpunk_hdr": create_cyberpunk,
"vintage_hdr": create_vintage,
"single_image": lambda imgs, s: create_single(imgs[0], s)
}
# Verifica se deve usar single image automaticamente
if len(imgs) == 1 and key != "single_image":
log_event("INFO", "Apenas 1 imagem detectada, usando Single Image automaticamente")
res, msg = create_single(imgs[0], s)
msg = "⚠️ Modo Single-Image Automático (apenas 1 foto detectada). " + msg
status_class = "status-warning"
else:
# Processa com a função específica
if key == "single_image" and len(imgs) > 1:
log_event("INFO", f"Usando apenas a primeira imagem de {len(imgs)} para Single Image")
res, msg = funcs[key]([imgs[0]], s)
else:
res, msg = funcs[key](imgs, s)
status_class = "status-success"
# Salva resultado
temp_path = tempfile.NamedTemporaryFile(
delete=False,
suffix=".png",
prefix="hdr_output_"
).name
res.save(temp_path, "PNG", quality=100, optimize=True)
log_event("SUCCESS", f"Processamento {key} concluído com sucesso")
return (
orig,
res,
f'<div class="status-box {status_class}">✅ {msg}</div>',
temp_path
)
except Exception as e:
error_msg = str(e)
log_event("ERROR", f"Erro no processamento: {error_msg}\n{traceback.format_exc()}")
return (
None,
None,
f'<div class="status-box status-error">❌ Erro: {error_msg}</div>',
None
)
# Conecta o botão ao processamento
apply_button.click(
process,
inputs=[image_input, hdr_select, strength_slider],
outputs=[original_preview, hdr_preview, status_output, download_file]
)
return demo
if __name__ == "__main__":
log_event("INFO", "Iniciando HDR Creator Pro Ultra v1.2.0")
demo = create_interface()
demo.launch(
server_name="0.0.0.0",
server_port=7860,
share=False,
show_error=True
)