File size: 4,658 Bytes
fdae55a
 
 
 
 
 
 
 
 
65a3cf4
 
 
 
 
fdae55a
 
 
 
 
 
e6cea74
fdae55a
 
 
 
e6cea74
fdae55a
 
 
 
 
e6cea74
fdae55a
 
 
 
 
 
 
65a3cf4
fdae55a
 
65a3cf4
fdae55a
65a3cf4
fdae55a
 
 
65a3cf4
fdae55a
 
 
 
 
 
 
 
 
 
 
 
65a3cf4
fdae55a
6254392
 
fdae55a
 
6254392
 
 
 
 
 
 
fdae55a
 
65a3cf4
fdae55a
65a3cf4
fdae55a
 
 
0475f02
fdae55a
 
0475f02
fdae55a
 
0475f02
 
fdae55a
 
0475f02
 
65a3cf4
0475f02
fdae55a
0475f02
fdae55a
65a3cf4
fdae55a
0475f02
 
 
fdae55a
 
0475f02
 
 
65a3cf4
0475f02
fdae55a
0475f02
fdae55a
65a3cf4
fdae55a
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
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()