Skydata001 commited on
Commit
3dcf03f
·
verified ·
1 Parent(s): b8d6d6c

Delete sprite_cutter.py

Browse files
Files changed (1) hide show
  1. sprite_cutter.py +0 -476
sprite_cutter.py DELETED
@@ -1,476 +0,0 @@
1
- import os
2
- import sys
3
- import cv2
4
- import numpy as np
5
- import torch
6
- import torchvision.transforms as transforms
7
- from PIL import Image, ImageDraw
8
- import zipfile
9
- from pathlib import Path
10
- import tempfile
11
- import shutil
12
- from scipy import ndimage
13
- from datetime import datetime
14
- import traceback
15
-
16
- try:
17
- import gradio as gr
18
- except ImportError:
19
- print("❌ Gradio not installed. Installing...")
20
- os.system("pip install -q gradio torch torchvision pillow opencv-python scipy numpy")
21
- import gradio as gr
22
-
23
- DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu")
24
- TEMP_DIR = tempfile.mkdtemp(prefix="sprite_cutter_")
25
-
26
- def cleanup_temp():
27
- """تنظيف الملفات المؤقتة"""
28
- if os.path.exists(TEMP_DIR):
29
- shutil.rmtree(TEMP_DIR)
30
-
31
- def remove_background_ai(image_cv2):
32
- """
33
- حذف الخلفية باستخدام Color Thresholding + Morphological Operations
34
- يعمل بسرعة عالية على الـ CPU
35
- """
36
- try:
37
- if len(image_cv2.shape) == 2:
38
- image_cv2 = cv2.cvtColor(image_cv2, cv2.COLOR_GRAY2BGR)
39
-
40
- bgr = image_cv2.copy()
41
- hsv = cv2.cvtColor(bgr, cv2.COLOR_BGR2HSV)
42
-
43
- # كشف الألوان الموحدة (الخلفيات النموذجية للـ Pixel Art)
44
- lower_blue = np.array([90, 100, 100])
45
- upper_blue = np.array([130, 255, 255])
46
-
47
- lower_pink = np.array([140, 50, 50])
48
- upper_pink = np.array([180, 255, 255])
49
-
50
- mask_blue = cv2.inRange(hsv, lower_blue, upper_blue)
51
- mask_pink = cv2.inRange(hsv, lower_pink, upper_pink)
52
-
53
- # دمج الأقنعة
54
- mask = cv2.bitwise_or(mask_blue, mask_pink)
55
-
56
- # تطبيق عمليات مورفولوجية
57
- kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3))
58
- mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel, iterations=1)
59
- mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel, iterations=1)
60
-
61
- # عكس القناع (الكائنات = أبيض)
62
- mask = cv2.bitwise_not(mask)
63
-
64
- # تطبيق القناع على الصورة
65
- result = cv2.bitwise_and(bgr, bgr, mask=mask)
66
- result = cv2.cvtColor(result, cv2.COLOR_BGR2RGBA)
67
- result[:, :, 3] = mask
68
-
69
- return result, mask
70
- except Exception as e:
71
- print(f"❌ خطأ في حذف الخلفية: {e}")
72
- return None, None
73
-
74
- def detect_sprites_gpu(image_array, mask):
75
- """
76
- كشف الكائنات باستخدام Connected Components على الـ GPU
77
- """
78
- try:
79
- if mask is None:
80
- return []
81
-
82
- # تطبيق Connected Components
83
- labeled, num_features = ndimage.label(mask > 128)
84
-
85
- sprites_info = []
86
-
87
- for i in range(1, num_features + 1):
88
- component = (labeled == i).astype(np.uint8) * 255
89
-
90
- # حساب الإحصائيات
91
- contours, _ = cv2.findContours(component, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
92
-
93
- if not contours:
94
- continue
95
-
96
- cnt = max(contours, key=cv2.contourArea)
97
- area = cv2.contourArea(cnt)
98
-
99
- # تصفية الكائنات الصغيرة جداً
100
- if area < 10:
101
- continue
102
-
103
- x, y, w, h = cv2.boundingRect(cnt)
104
-
105
- # تحديد الأبعاد لأقرب 2 بكسل (محاذاة للـ pixel grid)
106
- w = max(w, 2)
107
- h = max(h, 2)
108
-
109
- sprites_info.append({
110
- 'x': int(x),
111
- 'y': int(y),
112
- 'w': int(w),
113
- 'h': int(h),
114
- 'area': float(area),
115
- 'label': i
116
- })
117
-
118
- return sorted(sprites_info, key=lambda s: (s['y'], s['x']))
119
- except Exception as e:
120
- print(f"❌ خطأ في كشف الكائنات: {e}")
121
- traceback.print_exc()
122
- return []
123
-
124
- def extract_sprites(image_cv2, sprites_info, preview=False):
125
- """
126
- استخراج الكائنات وإرجاع الصور المقصوصة
127
- """
128
- extracted = []
129
- preview_image = image_cv2.copy()
130
-
131
- try:
132
- colors = [
133
- (0, 255, 0),
134
- (255, 0, 0),
135
- (0, 0, 255),
136
- (255, 255, 0),
137
- (255, 0, 255),
138
- (0, 255, 255)
139
- ]
140
-
141
- for idx, sprite in enumerate(sprites_info):
142
- x, y, w, h = sprite['x'], sprite['y'], sprite['w'], sprite['h']
143
-
144
- # استخراج الكائن
145
- sprite_img = image_cv2[y:y+h, x:x+w].copy()
146
-
147
- extracted.append({
148
- 'image': sprite_img,
149
- 'filename': f"sprite_{idx+1:03d}_{w}x{h}.png",
150
- 'data': sprite
151
- })
152
-
153
- # رسم المربعات على الصورة المعاينة
154
- if preview:
155
- color = colors[idx % len(colors)]
156
- cv2.rectangle(preview_image, (x, y), (x+w, y+h), color, 2)
157
- cv2.putText(preview_image, str(idx+1), (x+3, y+15), cv2.FONT_HERSHEY_SIMPLEX, 0.4, color, 1)
158
-
159
- return extracted, preview_image
160
- except Exception as e:
161
- print(f"❌ خطأ في استخراج الكائنات: {e}")
162
- return extracted, preview_image
163
-
164
- def create_zip_archive(sprites_list):
165
- """
166
- إنشاء ملف ZIP يحتوي على جميع الكائنات المقصوصة
167
- """
168
- try:
169
- zip_path = os.path.join(TEMP_DIR, f"sprites_{datetime.now().strftime('%Y%m%d_%H%M%S')}.zip")
170
-
171
- with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
172
- for idx, sprite in enumerate(sprites_list):
173
- img = sprite['image']
174
- filename = sprite['filename']
175
-
176
- # تحويل إلى RGB إذا لزم الأمر
177
- if len(img.shape) == 2:
178
- img = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
179
- elif img.shape[2] == 4:
180
- img = cv2.cvtColor(img, cv2.COLOR_BGRA2BGR)
181
-
182
- # حفظ في الذاكرة
183
- success, buffer = cv2.imencode('.png', img)
184
- if success:
185
- zipf.writestr(filename, buffer.tobytes())
186
-
187
- return zip_path
188
- except Exception as e:
189
- print(f"❌ خطأ في إنشاء ZIP: {e}")
190
- traceback.print_exc()
191
- return None
192
-
193
- def process_sprite_sheet(image_file, sensitivity_slider=0.5):
194
- """
195
- المعالج الرئيسي الذي يربط كل المراحل
196
- """
197
- try:
198
- if image_file is None:
199
- return None, None, None, "❌ من فضلك حمل صورة أولاً"
200
-
201
- # قراءة الصورة
202
- image_path = image_file if isinstance(image_file, str) else image_file
203
- image_cv2 = cv2.imread(image_path)
204
-
205
- if image_cv2 is None:
206
- return None, None, None, "❌ فشل تحميل الصورة"
207
-
208
- image_cv2 = cv2.cvtColor(image_cv2, cv2.COLOR_BGR2RGB)
209
-
210
- # حذف الخلفية
211
- image_rgba, mask = remove_background_ai(image_cv2)
212
- if image_rgba is None:
213
- return None, None, None, "❌ خطأ في حذف الخلفية"
214
-
215
- # كشف الكائنات
216
- sprites_info = detect_sprites_gpu(image_cv2, mask)
217
-
218
- if not sprites_info:
219
- return None, None, None, "⚠️ لم يتم كشف أي كائنات. حاول صورة مختلفة"
220
-
221
- # استخراج الكائنات
222
- extracted_sprites, preview = extract_sprites(image_cv2, sprites_info, preview=True)
223
-
224
- # إنشاء ZIP
225
- zip_path = create_zip_archive(extracted_sprites)
226
-
227
- # إعداد رسالة النتيجة
228
- stats_msg = f"""
229
- ✅ **تم بنجاح!**
230
-
231
- 📊 الإحصائيات:
232
- • عدد الكائنات المكتشفة: **{len(sprites_info)}**
233
- • أبعاد الصورة الأصلية: **{image_cv2.shape[1]}x{image_cv2.shape[0]}** بكسل
234
- • متوسط حجم الكائن: **{int(np.mean([s['area'] for s in sprites_info]))}** بكسل²
235
-
236
- 📦 الملف جاهز للتحميل!
237
- """
238
-
239
- preview_rgb = cv2.cvtColor(preview, cv2.COLOR_BGR2RGB)
240
-
241
- return preview_rgb, extracted_sprites[0]['image'] if extracted_sprites else None, zip_path, stats_msg
242
-
243
- except Exception as e:
244
- error_msg = f"❌ خطأ: {str(e)}\n{traceback.format_exc()}"
245
- print(error_msg)
246
- return None, None, None, error_msg
247
-
248
- def create_ui():
249
- """
250
- إنشاء واجهة Gradio احترافية وجميلة
251
- """
252
-
253
- css = """
254
- :root {
255
- --primary: #ff006e;
256
- --secondary: #00d9ff;
257
- --dark: #0a0e27;
258
- --card: #1a1f3a;
259
- --text: #e0e0ff;
260
- }
261
-
262
- body {
263
- background: linear-gradient(135deg, var(--dark) 0%, #1a0033 100%);
264
- font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
265
- color: var(--text);
266
- }
267
-
268
- #main_container {
269
- background: rgba(10, 14, 39, 0.95);
270
- border: 2px solid var(--secondary);
271
- border-radius: 15px;
272
- padding: 30px;
273
- box-shadow: 0 0 50px rgba(0, 217, 255, 0.3);
274
- }
275
-
276
- .title-section {
277
- text-align: center;
278
- margin-bottom: 30px;
279
- padding-bottom: 20px;
280
- border-bottom: 2px solid var(--primary);
281
- }
282
-
283
- .title-section h1 {
284
- margin: 0;
285
- font-size: 2.5em;
286
- background: linear-gradient(45deg, var(--secondary), var(--primary));
287
- -webkit-background-clip: text;
288
- -webkit-text-fill-color: transparent;
289
- background-clip: text;
290
- text-shadow: 0 0 20px rgba(0, 217, 255, 0.5);
291
- }
292
-
293
- .subtitle {
294
- color: rgba(224, 224, 255, 0.7);
295
- margin-top: 10px;
296
- }
297
-
298
- .control-panel {
299
- background: var(--card);
300
- border: 1px solid var(--secondary);
301
- border-radius: 10px;
302
- padding: 20px;
303
- margin-bottom: 20px;
304
- }
305
-
306
- .control-panel h3 {
307
- color: var(--secondary);
308
- margin-top: 0;
309
- }
310
-
311
- .slider {
312
- width: 100%;
313
- }
314
-
315
- .button-group {
316
- display: flex;
317
- gap: 10px;
318
- margin-top: 20px;
319
- }
320
-
321
- .button-group button {
322
- flex: 1;
323
- padding: 12px 20px;
324
- font-size: 1em;
325
- background: linear-gradient(45deg, var(--primary), #ff4081);
326
- color: white;
327
- border: none;
328
- border-radius: 8px;
329
- cursor: pointer;
330
- transition: all 0.3s ease;
331
- font-weight: bold;
332
- }
333
-
334
- .button-group button:hover {
335
- transform: translateY(-2px);
336
- box-shadow: 0 10px 25px rgba(255, 0, 110, 0.4);
337
- }
338
-
339
- .output-section {
340
- background: var(--card);
341
- border: 2px solid var(--secondary);
342
- border-radius: 10px;
343
- padding: 20px;
344
- margin-top: 20px;
345
- }
346
-
347
- .preview-image {
348
- border: 2px dashed var(--secondary);
349
- border-radius: 8px;
350
- padding: 10px;
351
- }
352
-
353
- .status-message {
354
- background: rgba(0, 217, 255, 0.1);
355
- border-left: 4px solid var(--secondary);
356
- padding: 15px;
357
- border-radius: 5px;
358
- margin-top: 15px;
359
- font-weight: 500;
360
- }
361
-
362
- .file-download {
363
- background: linear-gradient(45deg, var(--secondary), #00ffe0);
364
- color: var(--dark);
365
- padding: 15px;
366
- border-radius: 8px;
367
- text-align: center;
368
- font-weight: bold;
369
- margin-top: 15px;
370
- }
371
- """
372
-
373
- with gr.Blocks(css=css, theme=gr.themes.Soft()) as demo:
374
- with gr.Column(elem_id="main_container"):
375
- # Header
376
- with gr.Group():
377
- gr.HTML("""
378
- <div class="title-section">
379
- <h1>🎮 أداة قص البكسل التلقائية</h1>
380
- <p class="subtitle">
381
- استخراج تلقائي للكائنات من صور Sprite Sheet
382
- <br/>
383
- <em>AI-Powered Pixel Art Sprite Extraction</em>
384
- </p>
385
- </div>
386
- """)
387
-
388
- # Upload Section
389
- with gr.Group(elem_classes="control-panel"):
390
- gr.Markdown("### 📤 حمل صورة البكسل")
391
- image_input = gr.Image(
392
- label="صورة Sprite Sheet",
393
- type="filepath",
394
- accept_types=["image/*"],
395
- show_label=False
396
- )
397
- sensitivity = gr.Slider(
398
- minimum=0.1,
399
- maximum=1.0,
400
- value=0.5,
401
- step=0.1,
402
- label="📊 حساسية الكشف (Detection Sensitivity)",
403
- info="أعلى = كشف أدق لكائنات صغيرة"
404
- )
405
-
406
- # Process Button
407
- with gr.Group(elem_classes="control-panel"):
408
- process_btn = gr.Button(
409
- "🚀 ابدأ المعالجة",
410
- variant="primary",
411
- scale=1
412
- )
413
-
414
- # Results Section
415
- with gr.Group(elem_classes="output-section"):
416
- gr.Markdown("### 📊 النتائج")
417
-
418
- with gr.Tabs():
419
- with gr.TabItem("🔍 معاينة"):
420
- preview_image = gr.Image(
421
- label="صورة المعاينة (مع تحديد الكائنات)",
422
- type="numpy",
423
- elem_classes="preview-image"
424
- )
425
-
426
- with gr.TabItem("✂️ أول كائن"):
427
- first_sprite = gr.Image(
428
- label="أول كائن تم قصه",
429
- type="numpy",
430
- elem_classes="preview-image"
431
- )
432
-
433
- with gr.TabItem("📦 الملف"):
434
- zip_file = gr.File(
435
- label="تحميل جميع الكائنات (ZIP)",
436
- type="filepath"
437
- )
438
-
439
- # Status Message
440
- status_msg = gr.Markdown(
441
- "⏳ جاهز للعملية...",
442
- elem_classes="status-message"
443
- )
444
-
445
- # Footer
446
- gr.HTML("""
447
- <div style="text-align: center; margin-top: 30px; padding-top: 20px; border-top: 1px solid rgba(224, 224, 255, 0.2); color: rgba(224, 224, 255, 0.6); font-size: 0.9em;">
448
- ⚡ معالجة سريعة بقوة GPU | 🎨 دعم كامل للـ RTL العربي | 🔒 معالجة محلية آمنة
449
- <br/>
450
- <small>Version 1.0 | Built with ❤️ for Pixel Artists</small>
451
- </div>
452
- """)
453
-
454
- # Connect Events
455
- process_btn.click(
456
- fn=process_sprite_sheet,
457
- inputs=[image_input, sensitivity],
458
- outputs=[preview_image, first_sprite, zip_file, status_msg]
459
- )
460
-
461
- return demo
462
-
463
- if __name__ == "__main__":
464
- try:
465
- demo = create_ui()
466
- demo.launch(
467
- server_name="0.0.0.0",
468
- server_port=7860,
469
- share=False,
470
- show_error=True
471
- )
472
- except Exception as e:
473
- print(f"❌ خطأ في بدء التطبيق: {e}")
474
- traceback.print_exc()
475
- finally:
476
- cleanup_temp()