ImageResizeApp / app.py
luisaflorezm's picture
Update app.py
30922a9 verified
import gradio as gr
import numpy as np
import cv2
from PIL import Image
import os
import shutil # Para manejar directorios y archivos
# Importa tu módulo resize_utils
# Asegúrate de que 'app' sea el paquete correcto o ajusta la importación
from resize_utils import resize_image_for_print
# --- Tema y CSS personalizados ---
theme = gr.themes.Default(
primary_hue=gr.themes.Color(
c50="#e0f2f7", c100="#b3e1ef", c200="#80cee7", c300="#4dbbde", c400="#26ace0",
c500="#034077", # Tu azul principal
c600="#023768", c700="#022e59", c800="#01254a", c900="#011b3b", c950="#000f22"
),
secondary_hue=gr.themes.Color(
c50="#f9fafb", c100="#f4f6f8", c200="#e9ecef", c300="#dee2e6", c400="#ced4da",
c500="#adb5bd", c600="#889096", c700="#6c757d", c800="#495057", c900="#343a40",
c950="#212529"
),
neutral_hue=gr.themes.Color(
c50="#f8f9fa", c100="#f1f3f5", c200="#e9ecef", c300="#dee2e6", c400="#ced4da",
c500="#adb5bd", c600="#889096", c700="#6c757d", c800="#495057", c900="#343a40",
c950="#212529"
)
).set(
button_primary_background_fill="#034077",
button_primary_background_fill_hover="#023768",
button_primary_text_color="white",
checkbox_background_color_selected="#034077",
checkbox_border_color_selected="#034077",
slider_color="#034077",
)
css = """
body {
background-color: #f0f2f5;
}
.gradio-container {
max-width: 1200px;
margin: 30px auto;
border-radius: 15px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
background-color: white;
overflow: hidden;
}
h1 {
color: #034077;
text-align: center;
padding-top: 20px;
margin-bottom: 20px;
font-size: 2.5em;
font-weight: 700;
}
.gr-text-input, .gr-slider {
border-radius: 8px;
}
.gr-button.primary {
border-radius: 10px;
font-weight: bold;
padding: 10px 20px;
font-size: 1.1em;
transition: all 0.3s ease;
}
.gr-button.primary:hover {
transform: translateY(-2px);
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);
}
.gr-checkbox-group label {
font-weight: 500;
}
.gr-accordion {
border-radius: 10px;
border: 1px solid #e0e0e0;
margin-bottom: 15px;
background-color: #ffffff;
}
.gr-accordion.open > .gr-accordion-header {
border-bottom: 1px solid #e0e0e0;
}
.gr-accordion-header {
background-color: #f8f8f8;
border-top-left-radius: 10px;
border-top-right-radius: 10px;
padding: 15px;
font-weight: bold;
color: #034077;
font-size: 1.1em;
}
.gr-accordion-content .gr-checkbox label,
.gr-accordion-content .gr-slider label,
.gr-accordion-content .gr-textbox label {
color: #333333;
font-weight: 500;
margin-bottom: 5px;
}
.gr-image {
border: 1px solid #e0e0e0;
border-radius: 10px;
background-color: #fdfdfd;
}
.output_image_label {
text-align: center;
font-weight: bold;
color: #034077;
margin-top: 10px;
font-size: 1.2em;
}
"""
# Directorio temporal para guardar los resultados
OUTPUT_DIR = "resized_images_output"
os.makedirs(OUTPUT_DIR, exist_ok=True)
def process_images_for_print(
image_files: list, # Ahora espera una lista de archivos
input_folder: str, # Ruta a una carpeta
target_dpi: int,
longest_side_cm: float
) -> tuple: # Devolverá un listado de archivos para descarga y un mensaje
"""
Procesa una o varias imágenes (o una carpeta) para redimensionar y guarda como TIFF.
"""
processed_file_paths = []
output_messages = []
# Limpiar el directorio de salida antes de cada procesamiento
# Comentar esta línea si deseas que los archivos se acumulen entre ejecuciones.
# Pero para una interfaz de usuario limpia, es mejor borrarlos.
if os.path.exists(OUTPUT_DIR):
shutil.rmtree(OUTPUT_DIR)
os.makedirs(OUTPUT_DIR, exist_ok=True)
images_to_process = []
# Manejar entrada de carpeta
if input_folder and os.path.isdir(input_folder):
output_messages.append(f"Procesando imágenes de la carpeta: {input_folder}")
for filename in os.listdir(input_folder):
file_path = os.path.join(input_folder, filename)
if os.path.isfile(file_path) and filename.lower().endswith(('.png', '.jpg', '.jpeg', '.bmp', '.tiff', '.tif')):
images_to_process.append(file_path)
# Manejar entrada de múltiples archivos (si no se seleccionó carpeta o si también hay archivos individuales)
# Si se seleccionaron archivos individuales, se añaden a la lista.
# Gradio `gr.File(file_count="multiple")` devuelve una lista de objetos `tempfile.NamedTemporaryFile`
if image_files:
for img_obj in image_files:
if img_obj is not None:
images_to_process.append(img_obj.name) # Usar el atributo .name para obtener la ruta del archivo temporal
if not images_to_process:
raise gr.Error("Por favor, sube al menos una imagen o selecciona una carpeta con imágenes.")
for i, img_path in enumerate(images_to_process):
try:
# Obtener el nombre original del archivo desde la ruta
original_filename_ext = os.path.basename(img_path)
original_filename_no_ext = os.path.splitext(original_filename_ext)[0]
# Abrir la imagen con PIL
original_pil_img = Image.open(img_path)
# Convertir a BGR numpy array para resize_image_for_print
img_np_rgb = np.array(original_pil_img)
img_np_bgr = cv2.cvtColor(img_np_rgb, cv2.COLOR_RGB2BGR)
# Aplicar redimensionamiento
resized_np_bgr = resize_image_for_print(
img_np_bgr,
target_dpi=target_dpi,
target_long_side_cm=longest_side_cm
)
# Convertir de nuevo a PIL Image para guardar como TIFF con DPI
resized_pil_rgb = Image.fromarray(cv2.cvtColor(resized_np_bgr, cv2.COLOR_BGR2RGB))
# Guardar la imagen en formato TIFF con la información de DPI
output_filename = f"{original_filename_no_ext}_resized.tiff"
output_filepath = os.path.join(OUTPUT_DIR, output_filename)
# PIL guarda DPI en puntos por pulgada (dots per inch)
# Asegúrate de que los metadatos de DPI se establezcan correctamente al guardar
resized_pil_rgb.save(output_filepath, dpi=(target_dpi, target_dpi))
processed_file_paths.append(output_filepath)
output_messages.append(f"✅ Procesado: {original_filename_ext} -> {output_filename}")
except Exception as e:
output_messages.append(f"❌ Error al procesar {os.path.basename(img_path)}: {e}")
print(f"Error processing {img_path}: {e}")
if not processed_file_paths:
raise gr.Error("No se pudo procesar ninguna imagen. Revisa los archivos de entrada y los parámetros.")
# Gradio requiere que se devuelva una lista de rutas de archivo para gr.File(file_count="multiple")
# Y un mensaje para el Textbox
return processed_file_paths, "\n".join(output_messages)
with gr.Blocks(theme=theme, css=css, title="Redimensionador de Imágenes para Impresión") as demo:
gr.Markdown(
"""
# 🖼️ **Redimensionador de Imágenes para Impresión**
Sube tus imágenes o una carpeta, define los parámetros de impresión y obtén tus imágenes redimensionadas en formato TIFF.
"""
)
with gr.Row():
with gr.Column(scale=1):
with gr.Group(elem_classes="input-card"):
gr.Markdown("## ⬆️ **Cargar Imágenes**")
# Opción para subir múltiples archivos
image_files_input = gr.File(
label="Seleccionar una o varias imágenes",
file_count="multiple", # Permite múltiples archivos
type="filepath", # *** CORREGIDO A 'filepath' ***
file_types=[".png", ".jpg", ".jpeg", ".bmp", ".tiff", ".tif"],
height=200
)
gr.Markdown("---")
# Opción para seleccionar una carpeta
input_folder_path = gr.Textbox(
label="O ingresar la ruta a una carpeta con imágenes",
placeholder="Ej: /ruta/a/mis/fotos (dejar en blanco si subes archivos)",
interactive=True
)
gr.Markdown("*(Si seleccionas archivos y una carpeta, se procesarán AMBOS.)*")
with gr.Group(elem_classes="options-card"):
gr.Markdown("## ⚙️ **Parámetros de Impresión**")
longest_side_cm = gr.Slider(
minimum=1, maximum=200, step=1, value=50,
label="Lado más largo (cm)",
info="Define el tamaño físico del lado más largo de la imagen resultante en centímetros.",
interactive=True
)
target_dpi = gr.Slider(
minimum=72, maximum=1200, step=10, value=300,
label="DPI (Puntos por pulgada)",
info="Resolución deseada para impresión (300 DPI es estándar para alta calidad).",
interactive=True
)
process_button = gr.Button("¡Redimensionar y Guardar como TIFF! 🚀", variant="primary", scale=0)
with gr.Column(scale=1):
with gr.Group(elem_classes="output-card"):
gr.Markdown("## ✅ **Resultados Procesados**")
# Para mostrar el estado del procesamiento
status_output = gr.Textbox(
label="Estado del Proceso",
lines=10,
interactive=False,
max_lines=20,
show_copy_button=True
)
# Para descargar los archivos procesados
output_files = gr.File(
label="Imágenes TIFF Redimensionadas",
file_count="multiple", # Permite descargar múltiples archivos
type="filepath", # *** CORREGIDO A 'filepath' ***
interactive=False # No permite al usuario subir archivos aquí
)
gr.Markdown("---")
gr.Markdown("✨*Tus imágenes redimensionadas aparecerán aquí. Haz clic para descargar.*")
# Acción del botón
process_button.click(
fn=process_images_for_print,
inputs=[
image_files_input,
input_folder_path,
target_dpi,
longest_side_cm
],
outputs=[output_files, status_output]
)
# FastAPI + Uvicorn
if __name__ == '__main__':
demo.launch(debug=True, show_api=False, server_name="0.0.0.0", server_port=7860)