MynooMuz commited on
Commit
fdae55a
·
1 Parent(s): 21a9515

feat: initial normal map generator with folder support

Browse files
Files changed (2) hide show
  1. app.py +111 -0
  2. requirements.txt +4 -0
app.py ADDED
@@ -0,0 +1,111 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import cv2
3
+ import numpy as np
4
+ from PIL import Image
5
+ import os
6
+ import zipfile
7
+ import tempfile
8
+ import shutil
9
+
10
+ def process_image(img, strength):
11
+ # Görseli gri tonlamaya çevir ve normalize et
12
+ img_cv = np.array(img.convert('L'))
13
+ img_cv = img_cv.astype(np.float32) / 255.0
14
+
15
+ # Sobel filtresi ile X ve Y yönlerindeki gradyanları (eğimleri) hesapla
16
+ sobelx = cv2.Sobel(img_cv, cv2.CV_64F, 1, 0, ksize=3)
17
+ sobely = cv2.Sobel(img_cv, cv2.CV_64F, 0, 1, ksize=3)
18
+
19
+ # Normal vektörü bileşenlerini hesapla (-dz/dx, -dz/dy, 1)
20
+ nx = -sobelx * strength
21
+ ny = -sobely * strength
22
+ nz = np.ones_like(img_cv)
23
+
24
+ # Vektörleri normalize et (uzunluklarını 1 yap)
25
+ length = np.sqrt(nx**2 + ny**2 + nz**2)
26
+ nx /= length
27
+ ny /= length
28
+ nz /= length
29
+
30
+ # [-1, 1] aralığını RGB için [0, 255] aralığına çevir
31
+ nx = (nx + 1) / 2 * 255
32
+ ny = (ny + 1) / 2 * 255
33
+ nz = (nz + 1) / 2 * 255
34
+
35
+ # R=X, G=Y, B=Z olacak şekilde birleştir
36
+ normal_map = np.stack((nx, ny, nz), axis=-1).astype(np.uint8)
37
+ return Image.fromarray(normal_map)
38
+
39
+ def process_single(image, strength):
40
+ if image is None:
41
+ return None
42
+ return process_image(image, strength)
43
+
44
+ def process_files(file_objs, strength):
45
+ if not file_objs:
46
+ return None
47
+
48
+ # Geçici bir klasör ve ZIP dosyası oluştur
49
+ temp_dir = tempfile.mkdtemp()
50
+ zip_filename = "normal_maps.zip"
51
+ zip_path = os.path.join(tempfile.gettempdir(), zip_filename)
52
+
53
+ with zipfile.ZipFile(zip_path, 'w') as zipf:
54
+ for file_obj in file_objs:
55
+ # Gradio dosya yollarını liste olarak verir
56
+ file_path = file_obj.name if hasattr(file_obj, 'name') else file_obj
57
+ file_name = os.path.basename(file_path)
58
+
59
+ if file_name.lower().endswith(('.png', '.jpg', '.jpeg', '.bmp', '.webp')):
60
+ try:
61
+ img = Image.open(file_path)
62
+ normal_map = process_image(img, strength)
63
+
64
+ out_name = f"normal_{file_name}"
65
+ if not out_name.lower().endswith('.png'):
66
+ out_name = os.path.splitext(out_name)[0] + '.png'
67
+
68
+ out_path = os.path.join(temp_dir, out_name)
69
+ normal_map.save(out_path, format="PNG")
70
+ zipf.write(out_path, arcname=out_name)
71
+ except Exception as e:
72
+ print(f"Hata ({file_path}): {e}")
73
+
74
+ # Geçici klasörü temizle
75
+ shutil.rmtree(temp_dir)
76
+ return zip_path
77
+
78
+ # Gradio Arayüzü
79
+ with gr.Blocks(title="Normal Map Generator", theme=gr.themes.Soft()) as demo:
80
+ gr.Markdown("# 🗺️ Normal Map Generator")
81
+ gr.Markdown("Standart görsellerden 3D modelleme ve texturing için normal map'ler oluşturun.")
82
+
83
+ with gr.Tabs():
84
+ # TEKLİ GÖRSEL SEKMESİ
85
+ with gr.Tab("Tekli Görsel"):
86
+ with gr.Row():
87
+ with gr.Column():
88
+ img_input = gr.Image(type="pil", label="Girdi Görseli")
89
+ strength_slider_1 = gr.Slider(minimum=0.1, maximum=10.0, value=2.0, step=0.1, label="Derinlik Şiddeti (Strength)")
90
+ btn_single = gr.Button("Oluştur", variant="primary")
91
+ with gr.Column():
92
+ img_output = gr.Image(type="pil", label="Normal Map Sonucu", format="png")
93
+
94
+ btn_single.click(fn=process_single, inputs=[img_input, strength_slider_1], outputs=img_output)
95
+
96
+ # KLASÖR/TOPLU İŞLEM SEKMESİ
97
+ with gr.Tab("Klasör (Toplu İşlem)"):
98
+ gr.Markdown("Bir klasör seçin veya birden fazla görseli sürükleyip bırakın. Tüm görseller işlenip size bir ZIP dosyası olarak sunulacaktır.")
99
+ with gr.Row():
100
+ with gr.Column():
101
+ # file_count="directory" klasör yüklemeye izin verir
102
+ folder_input = gr.File(file_count="directory", label="Klasör / Dosyalar Yükle")
103
+ strength_slider_2 = gr.Slider(minimum=0.1, maximum=10.0, value=2.0, step=0.1, label="Derinlik Şiddeti (Strength)")
104
+ btn_folder = gr.Button("Toplu Oluştur ve İndir", variant="primary")
105
+ with gr.Column():
106
+ file_output = gr.File(label="İndirilebilir ZIP Dosyası")
107
+
108
+ btn_folder.click(fn=process_files, inputs=[folder_input, strength_slider_2], outputs=file_output)
109
+
110
+ if __name__ == "__main__":
111
+ demo.launch()
requirements.txt ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ gradio
2
+ opencv-python-headless
3
+ numpy
4
+ pillow