Download app.py from MynooMuz/NormalMap: direct link, hf CLI and curl.
- Browser
- Download file 4.66 kB
-
https://huggingface.co/spaces/MynooMuz/NormalMap/resolve/main/app.py
- Command line
-
hf download hf://spaces/MynooMuz/NormalMap/app.py
-
curl -L -o app.py https://huggingface.co/spaces/MynooMuz/NormalMap/resolve/main/app.py
4.66 kB
| import gradio as gr | |
| import cv2 | |
| import numpy as np | |
| from PIL import Image | |
| import os | |
| import zipfile | |
| import tempfile | |
| import shutil | |
| def process_image(img, strength, upscale=False): | |
| if upscale: | |
| new_size = (img.width * 2, img.height * 2) | |
| img = img.resize(new_size, resample=Image.LANCZOS) | |
| img_cv = np.array(img.convert('L')) | |
| img_cv = img_cv.astype(np.float32) / 255.0 | |
| sobelx = cv2.Sobel(img_cv, cv2.CV_64F, 1, 0, ksize=3) | |
| sobely = cv2.Sobel(img_cv, cv2.CV_64F, 0, 1, ksize=3) | |
| nx = -sobelx * strength | |
| ny = -sobely * strength | |
| nz = np.ones_like(img_cv) | |
| length = np.sqrt(nx**2 + ny**2 + nz**2) | |
| nx /= length | |
| ny /= length | |
| nz /= length | |
| nx = (nx + 1) / 2 * 255 | |
| ny = (ny + 1) / 2 * 255 | |
| nz = (nz + 1) / 2 * 255 | |
| normal_map = np.stack((nx, ny, nz), axis=-1).astype(np.uint8) | |
| return Image.fromarray(normal_map) | |
| def process_single(image, strength, upscale): | |
| if image is None: | |
| return None | |
| return process_image(image, strength, upscale) | |
| def process_files(file_objs, strength, upscale): | |
| if not file_objs: | |
| return None | |
| # Create temp directory and ZIP file | |
| temp_dir = tempfile.mkdtemp() | |
| zip_filename = "normal_maps.zip" | |
| zip_path = os.path.join(tempfile.gettempdir(), zip_filename) | |
| with zipfile.ZipFile(zip_path, 'w') as zipf: | |
| for file_obj in file_objs: | |
| file_path = file_obj.name if hasattr(file_obj, 'name') else file_obj | |
| file_name = os.path.basename(file_path) | |
| if file_name.lower().endswith(('.png', '.jpg', '.jpeg', '.bmp', '.webp')): | |
| try: | |
| img = Image.open(file_path) | |
| normal_map = process_image(img, strength, upscale) | |
| base_name, ext = os.path.splitext(file_name) | |
| out_name = f"{base_name}_normalmap{ext}" | |
| out_path = os.path.join(temp_dir, out_name) | |
| try: | |
| normal_map.save(out_path) | |
| except: | |
| out_name = f"{base_name}_normalmap.png" | |
| out_path = os.path.join(temp_dir, out_name) | |
| normal_map.save(out_path, format="PNG") | |
| zipf.write(out_path, arcname=out_name) | |
| except Exception as e: | |
| print(f"Error ({file_path}): {e}") | |
| # Cleanup temp directory | |
| shutil.rmtree(temp_dir) | |
| return zip_path | |
| # Gradio Interface | |
| with gr.Blocks(title="Normal Map Generator", theme=gr.themes.Soft()) as demo: | |
| gr.Markdown("# 🗺️ Normal Map Generator") | |
| gr.Markdown("Create normal maps from standard images for 3D modeling and texturing.") | |
| with gr.Tabs(): | |
| # SINGLE IMAGE TAB | |
| with gr.Tab("Single Image"): | |
| with gr.Row(): | |
| with gr.Column(): | |
| img_input = gr.Image(type="pil", label="Input Image") | |
| strength_slider_1 = gr.Slider(minimum=0.1, maximum=10.0, value=2.0, step=0.1, label="Normal Map Strength") | |
| upscale_check_1 = gr.Checkbox(label="Upscale 2x (High Quality Smoothing)", value=False) | |
| btn_single = gr.Button("Generate", variant="primary") | |
| with gr.Column(): | |
| img_output = gr.Image(type="pil", label="Normal Map Result", format="png") | |
| btn_single.click(fn=process_single, inputs=[img_input, strength_slider_1, upscale_check_1], outputs=img_output) | |
| # FOLDER/BATCH TAB | |
| with gr.Tab("Folder (Batch Processing)"): | |
| gr.Markdown("Select a folder or drag and drop multiple images. All images will be processed and provided as a ZIP file.") | |
| with gr.Row(): | |
| with gr.Column(): | |
| # file_count="directory" allows folder upload | |
| folder_input = gr.File(file_count="directory", label="Upload Folder / Files") | |
| strength_slider_2 = gr.Slider(minimum=0.1, maximum=10.0, value=2.0, step=0.1, label="Normal Map Strength") | |
| upscale_check_2 = gr.Checkbox(label="Upscale 2x (High Quality Smoothing)", value=False) | |
| btn_folder = gr.Button("Generate Batch and Download", variant="primary") | |
| with gr.Column(): | |
| file_output = gr.File(label="Download ZIP File") | |
| btn_folder.click(fn=process_files, inputs=[folder_input, strength_slider_2, upscale_check_2], outputs=file_output) | |
| if __name__ == "__main__": | |
| demo.launch() |