Spaces:
Sleeping
Sleeping
| 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) |