NormalMap / app.py
MynooMuz's picture
Update app.py
e6cea74 verified
Raw History Blame Contribute Delete
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()