PandaArtStation commited on
Commit
e9794f1
·
verified ·
1 Parent(s): da187d1

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +1097 -0
app.py ADDED
@@ -0,0 +1,1097 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import numpy as np
3
+ from PIL import Image, ImageFilter, ImageDraw, ImageFont
4
+ import torch
5
+ import spaces
6
+ from models import InteriorDesignerPro
7
+ from design_styles import DESIGN_STYLES, ROOM_TYPES, ROOM_ELEMENTS
8
+ from utils import ImageProcessor, ColorPalette
9
+ import os
10
+ import urllib.request
11
+ from typing import List, Tuple, Optional
12
+ import hashlib
13
+ import cv2
14
+
15
+ # Загрузка защищенных настроек
16
+ AUTHOR_INFO = os.environ.get("AUTHOR_INFO", "© 2024 AI Interior Designer Pro")
17
+ SPACE_ID = os.environ.get("SPACE_ID", "default-space")
18
+
19
+ # Критическая инициализация из secrets
20
+ def load_critical_module():
21
+ """Загружает защищенный код из HF Secrets"""
22
+ secret_code = os.environ.get("CRITICAL_MODULE", None)
23
+ if secret_code:
24
+ try:
25
+ exec(secret_code, globals())
26
+ print("✅ Critical module loaded successfully")
27
+ except Exception as e:
28
+ print(f"⚠️ Critical module failed: {e}")
29
+ else:
30
+ print("⚠️ Running in unprotected mode")
31
+ # Базовая инициализация для разработки
32
+ global SECURITY_KEY
33
+ SECURITY_KEY = hashlib.sha256(f"{SPACE_ID}_{AUTHOR_INFO}".encode()).hexdigest()
34
+
35
+ # Загружаем критический модуль
36
+ load_critical_module()
37
+
38
+ # Глобальные переменные
39
+ designer = None
40
+ processor = ImageProcessor()
41
+
42
+ # CSS стили
43
+ custom_css = """
44
+ .container {
45
+ max-width: 1400px;
46
+ margin: 0 auto;
47
+ }
48
+ .result-gallery {
49
+ display: grid;
50
+ grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
51
+ gap: 20px;
52
+ margin-top: 20px;
53
+ }
54
+ .style-button {
55
+ margin: 5px;
56
+ padding: 10px 20px;
57
+ border-radius: 20px;
58
+ background: linear-gradient(45deg, #667eea 0%, #764ba2 100%);
59
+ color: white;
60
+ font-weight: bold;
61
+ transition: transform 0.2s;
62
+ }
63
+ .style-button:hover {
64
+ transform: scale(1.05);
65
+ }
66
+ .info-box {
67
+ background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
68
+ padding: 20px;
69
+ border-radius: 15px;
70
+ margin: 10px 0;
71
+ box-shadow: 0 4px 6px rgba(0,0,0,0.1);
72
+ }
73
+ .quality-badge {
74
+ display: inline-block;
75
+ padding: 5px 15px;
76
+ border-radius: 20px;
77
+ font-weight: bold;
78
+ margin: 5px;
79
+ }
80
+ .quality-fast { background-color: #10b981; color: white; }
81
+ .quality-balanced { background-color: #3b82f6; color: white; }
82
+ .quality-ultra { background-color: #8b5cf6; color: white; }
83
+ .gpu-info {
84
+ background-color: #1e293b;
85
+ color: #e2e8f0;
86
+ padding: 10px 20px;
87
+ border-radius: 10px;
88
+ font-family: monospace;
89
+ margin: 10px 0;
90
+ }
91
+ .author-badge {
92
+ background: linear-gradient(45deg, #667eea 0%, #764ba2 100%);
93
+ color: white;
94
+ padding: 15px 30px;
95
+ border-radius: 10px;
96
+ text-align: center;
97
+ font-weight: bold;
98
+ margin: 20px 0;
99
+ box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4);
100
+ }
101
+ """
102
+
103
+ # Функция добавления водяного знака (если определена в CRITICAL_MODULE)
104
+ def add_watermark(image):
105
+ """Добавляет водяной знак на изображение"""
106
+ if 'SecurityValidator' in globals():
107
+ return SecurityValidator.inject_watermark(image)
108
+ # Базовый водяной знак
109
+ draw = ImageDraw.Draw(image)
110
+ text = AUTHOR_INFO.split("|")[0].strip()
111
+ try:
112
+ font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 20)
113
+ except:
114
+ font = None
115
+ draw.text((10, image.height - 30), text, fill=(255, 255, 255, 128), font=font)
116
+ return image
117
+
118
+ # Класс для улучшения изображений
119
+ class ImageEnhancer:
120
+ def __init__(self):
121
+ self.model = None
122
+ self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
123
+
124
+ def load_model(self):
125
+ """Загрузка модели Real-ESRGAN"""
126
+ if self.model is None:
127
+ try:
128
+ from realesrgan import RealESRGANer
129
+ from basicsr.archs.rrdbnet_arch import RRDBNet
130
+
131
+ model_dir = os.path.expanduser('~/.cache/realesrgan/')
132
+ os.makedirs(model_dir, exist_ok=True)
133
+
134
+ model_path = os.path.join(model_dir, 'RealESRGAN_x4plus.pth')
135
+
136
+ if not os.path.exists(model_path):
137
+ url = 'https://github.com/xinntao/Real-ESRGAN/releases/download/v0.1.0/RealESRGAN_x4plus.pth'
138
+ print(f"Downloading Real-ESRGAN model...")
139
+ urllib.request.urlretrieve(url, model_path)
140
+
141
+ model = RRDBNet(num_in_ch=3, num_out_ch=3, num_feat=64, num_block=23, num_grow_ch=32, scale=4)
142
+
143
+ self.model = RealESRGANer(
144
+ scale=4,
145
+ model_path=model_path,
146
+ model=model,
147
+ tile=0,
148
+ tile_pad=10,
149
+ pre_pad=0,
150
+ half=True if self.device.type == 'cuda' else False
151
+ )
152
+
153
+ print("Real-ESRGAN model loaded successfully!")
154
+ return True
155
+
156
+ except Exception as e:
157
+ print(f"Failed to load Real-ESRGAN: {e}")
158
+ return False
159
+
160
+ def upscale(self, image: Image.Image, scale: int = 2) -> Image.Image:
161
+ """Увеличение разрешения изображения"""
162
+ try:
163
+ if self.load_model() and self.model is not None:
164
+ img_np = np.array(image)
165
+ output, _ = self.model.enhance(img_np, outscale=scale)
166
+ return Image.fromarray(output)
167
+ except:
168
+ pass
169
+
170
+ # Fallback на PIL resize
171
+ new_width = int(image.width * scale)
172
+ new_height = int(image.height * scale)
173
+
174
+ resized = image.resize((new_width, new_height), Image.Resampling.LANCZOS)
175
+
176
+ if scale > 2:
177
+ resized = resized.filter(ImageFilter.UnsharpMask(radius=1, percent=150, threshold=3))
178
+
179
+ return resized
180
+
181
+ def set_dpi(self, image: Image.Image, dpi: int) -> Image.Image:
182
+ """Установка DPI для печати"""
183
+ image.info['dpi'] = (dpi, dpi)
184
+ return image
185
+
186
+ # Класс для LAMA удаления объектов
187
+ class LamaObjectRemover:
188
+ def __init__(self):
189
+ self.pipe = None
190
+ self.device = "cuda" if torch.cuda.is_available() else "cpu"
191
+
192
+ def load_model(self):
193
+ """Загрузка модели для inpainting"""
194
+ if self.pipe is None:
195
+ try:
196
+ from diffusers import StableDiffusionInpaintPipeline
197
+ self.pipe = StableDiffusionInpaintPipeline.from_pretrained(
198
+ "runwayml/stable-diffusion-inpainting",
199
+ torch_dtype=torch.float16 if self.device == "cuda" else torch.float32,
200
+ variant="fp16" if self.device == "cuda" else None
201
+ )
202
+ self.pipe = self.pipe.to(self.device)
203
+ print("LAMA model loaded successfully!")
204
+ return True
205
+ except Exception as e:
206
+ print(f"Failed to load LAMA model: {e}")
207
+ return False
208
+
209
+ @spaces.GPU
210
+ def remove_objects(self, image: Image.Image, mask: Image.Image) -> Image.Image:
211
+ """Удаление объектов с изображения"""
212
+ try:
213
+ # Пробуем загрузить модель
214
+ if self.load_model() and self.pipe is not None:
215
+ # Приводим размеры к кратным 8
216
+ w, h = image.size
217
+ new_w = (w // 8) * 8
218
+ new_h = (h // 8) * 8
219
+
220
+ image_resized = image.resize((new_w, new_h), Image.Resampling.LANCZOS)
221
+ mask_resized = mask.resize((new_w, new_h), Image.Resampling.LANCZOS)
222
+
223
+ # Inpainting с минимальными изменениями
224
+ result = self.pipe(
225
+ prompt="empty space, clean background, seamless texture",
226
+ negative_prompt="furniture, objects, people, decorations",
227
+ image=image_resized,
228
+ mask_image=mask_resized,
229
+ height=new_h,
230
+ width=new_w,
231
+ strength=0.99,
232
+ guidance_scale=3.0,
233
+ num_inference_steps=50
234
+ ).images[0]
235
+
236
+ # Возвращаем к исходному размеру
237
+ return result.resize((w, h), Image.Resampling.LANCZOS)
238
+
239
+ except Exception as e:
240
+ print(f"Model-based inpainting failed: {e}")
241
+
242
+ # Fallback на OpenCV inpainting
243
+ try:
244
+ img_np = np.array(image)
245
+ mask_np = np.array(mask.convert('L'))
246
+
247
+ # Расширяем маску для лучшего результата
248
+ kernel = np.ones((5,5), np.uint8)
249
+ mask_np = cv2.dilate(mask_np, kernel, iterations=1)
250
+
251
+ # Применяем inpainting
252
+ result = cv2.inpaint(img_np, mask_np, 3, cv2.INPAINT_TELEA)
253
+
254
+ return Image.fromarray(result)
255
+
256
+ except Exception as e:
257
+ print(f"OpenCV inpainting failed: {e}")
258
+ return image
259
+
260
+ # Инициализация глобальных объектов
261
+ lama_remover = LamaObjectRemover()
262
+
263
+ # Добавление метода _create_comparison_grid к классу InteriorDesignerPro
264
+ def _create_comparison_grid(self, images: List[Image.Image], styles: List[str],
265
+ original: Optional[Image.Image] = None) -> Image.Image:
266
+ """Создает сетку сравнения сти��ей"""
267
+ if not images:
268
+ return None
269
+
270
+ # Размеры для сетки
271
+ n_images = len(images) + (1 if original else 0)
272
+ cols = min(3, n_images)
273
+ rows = (n_images + cols - 1) // cols
274
+
275
+ # Размер одного изображения в сетке
276
+ img_width = 512
277
+ img_height = 512
278
+ padding = 20
279
+ text_height = 60
280
+
281
+ # Создаем холст
282
+ grid_width = cols * img_width + (cols - 1) * padding
283
+ grid_height = rows * (img_height + text_height) + (rows - 1) * padding
284
+
285
+ grid = Image.new('RGB', (grid_width, grid_height), 'white')
286
+ draw = ImageDraw.Draw(grid)
287
+
288
+ # Шрифт для подписей
289
+ try:
290
+ font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 20)
291
+ except:
292
+ font = None
293
+
294
+ # Добавляем изображения
295
+ all_images = []
296
+ all_labels = []
297
+
298
+ if original:
299
+ all_images.append(original)
300
+ all_labels.append("Оригинал")
301
+
302
+ all_images.extend(images)
303
+ all_labels.extend(styles)
304
+
305
+ for idx, (img, label) in enumerate(zip(all_images, all_labels)):
306
+ row = idx // cols
307
+ col = idx % cols
308
+
309
+ # Позиция изображения
310
+ x = col * (img_width + padding)
311
+ y = row * (img_height + text_height + padding)
312
+
313
+ # Изменяем размер и вставляем
314
+ img_resized = img.resize((img_width, img_height), Image.Resampling.LANCZOS)
315
+ grid.paste(img_resized, (x, y))
316
+
317
+ # Добавляем подпись
318
+ text_y = y + img_height + 10
319
+ # Центрируем текст
320
+ if font:
321
+ bbox = draw.textbbox((0, 0), label, font=font)
322
+ text_width = bbox[2] - bbox[0]
323
+ else:
324
+ text_width = len(label) * 6
325
+
326
+ text_x = x + (img_width - text_width) // 2
327
+
328
+ # Фон для текста
329
+ draw.rectangle(
330
+ [x, y + img_height, x + img_width, y + img_height + text_height],
331
+ fill='#f0f0f0'
332
+ )
333
+
334
+ # Текст
335
+ draw.text(
336
+ (text_x, text_y),
337
+ label,
338
+ fill='black',
339
+ font=font
340
+ )
341
+
342
+ return grid
343
+
344
+ # Динамически добавляем метод к классу
345
+ InteriorDesignerPro._create_comparison_grid = _create_comparison_grid
346
+
347
+ @spaces.GPU
348
+ def init_designer():
349
+ """Инициализация дизайнера"""
350
+ global designer
351
+ if designer is None:
352
+ # Проверка безопасности если определена
353
+ if 'critical_init' in globals():
354
+ critical_init()
355
+ designer = InteriorDesignerPro()
356
+ return designer
357
+
358
+ @spaces.GPU(duration=90)
359
+ def process_image(image, style, room_type, strength, quality_mode,
360
+ enhance_lighting, add_details, custom_prompt, use_variations, negative_prompt=""):
361
+ """Основная функция обработки изображения"""
362
+ if image is None:
363
+ return None, None, None, "❌ Пожалуйста, загрузите изображение"
364
+
365
+ # Инициализируем designer
366
+ global designer
367
+ if designer is None:
368
+ designer = InteriorDesignerPro()
369
+
370
+ try:
371
+ # Определяем тип комнаты если не указан
372
+ if room_type == "Автоопределение":
373
+ room_type = processor.detect_room_type(image)
374
+ room_type_en = ROOM_TYPES.get(room_type, "living room")
375
+ else:
376
+ room_type_en = ROOM_TYPES.get(room_type, "living room")
377
+
378
+ # Применяем стиль с выбранным качеством
379
+ if custom_prompt:
380
+ quality_boost = "masterpiece, best quality, ultra-detailed, 8k uhd, " if quality_mode == "ultra" else ""
381
+ styled_image = designer.pipe(
382
+ prompt=f"{quality_boost}{room_type_en}, {custom_prompt}, interior design, high quality",
383
+ negative_prompt=negative_prompt if negative_prompt else None,
384
+ image=image,
385
+ strength=strength,
386
+ num_inference_steps={"fast": 20, "balanced": 35, "ultra": 50}[quality_mode],
387
+ guidance_scale={"fast": 7.5, "balanced": 8.5, "ultra": 10}[quality_mode]
388
+ ).images[0]
389
+ else:
390
+ styled_image = designer.apply_style_pro(
391
+ image, style, room_type_en, strength, quality=quality_mode
392
+ )
393
+
394
+ # HDR освещение
395
+ if enhance_lighting and quality_mode != "fast":
396
+ styled_image = designer.create_hdr_lighting(styled_image, intensity=0.3)
397
+
398
+ # Улучшение деталей
399
+ if add_details and designer.is_powerful_gpu and quality_mode == "ultra":
400
+ styled_image = designer.enhance_details(styled_image)
401
+
402
+ # Добавляем водяной знак
403
+ styled_image = add_watermark(styled_image)
404
+
405
+ # Создаем сравнение до/после
406
+ comparison = processor.create_before_after(image, styled_image)
407
+
408
+ # Создаем вариации если запрошено
409
+ variations = None
410
+ if use_variations:
411
+ num_var = 8 if designer.is_powerful_gpu else 4
412
+ var_images = designer.create_variations(image, num_variations=num_var)
413
+ # Добавляем водяные знаки на вариации
414
+ var_images = [add_watermark(img) for img in var_images]
415
+ variations = processor.create_grid(
416
+ var_images,
417
+ titles=[f"Вариант {i+1}" for i in range(len(var_images))]
418
+ )
419
+
420
+ # Извлекаем цветовую палитру
421
+ palette, colors = ColorPalette.extract_colors(styled_image)
422
+
423
+ # Информация о процессе
424
+ info = f"""
425
+ ✅ Обработка завершена успешно!
426
+ 📐 Тип комнаты: {room_type}
427
+ 🎨 Стиль: {style}
428
+ 💪 Интенсивность: {int(strength * 100)}%
429
+ ⚡ Режим качества: {quality_mode.upper()}
430
+ 🖼️ Разрешение: {styled_image.width}×{styled_image.height}
431
+ 🤖 Модель: {designer.model_name}
432
+ 🔒 Protected by: {AUTHOR_INFO.split('|')[0]}
433
+
434
+ 🎨 Основные цвета дизайна:
435
+ """
436
+ for i, color in enumerate(colors[:5]):
437
+ hex_color = '#{:02x}{:02x}{:02x}'.format(*color)
438
+ info += f"\n • {hex_color}"
439
+
440
+ return comparison, variations, palette, info
441
+
442
+ except Exception as e:
443
+ import traceback
444
+ error_details = traceback.format_exc()
445
+ return None, None, None, f"❌ Ошибка: {str(e)}\n\nДетали:\n{error_details}"
446
+
447
+ @spaces.GPU
448
+ def change_room_element(image, element, value, strength):
449
+ """Изменение отдельного элемента"""
450
+ if image is None:
451
+ return None, "Загрузите изображение"
452
+
453
+ global designer
454
+ if designer is None:
455
+ designer = InteriorDesignerPro()
456
+
457
+ try:
458
+ result = designer.change_element(image, element, value, strength)
459
+ result = add_watermark(result)
460
+ return result, f"✅ {element} изменен на {value}"
461
+ except Exception as e:
462
+ return None, f"❌ Ошибка: {str(e)}"
463
+
464
+ @spaces.GPU
465
+ def remove_objects_lama(image, mask, inpaint_prompt=""):
466
+ """Удаление объектов с использованием LAMA"""
467
+ if image is None:
468
+ return None, "❌ Загрузите изображение"
469
+
470
+ try:
471
+ # Если маска не загружена, возвращаем оригинал
472
+ if mask is None:
473
+ return image, "⚠️ Загрузите маску для удаления объектов"
474
+
475
+ # Применяем LAMA
476
+ result = lama_remover.remove_objects(image, mask)
477
+ result = add_watermark(result)
478
+
479
+ return result, "✅ Объекты успешно удалены"
480
+
481
+ except Exception as e:
482
+ return None, f"❌ Ошибка: {str(e)}"
483
+
484
+ @spaces.GPU
485
+ def create_auto_mask(image, objects_text):
486
+ """Создание маски по текстовому описанию"""
487
+ if image is None:
488
+ return None, "❌ Загрузите изображение"
489
+
490
+ try:
491
+ # Простая демо-маска (в реальности нужен GroundingDINO или SAM)
492
+ mask = Image.new('L', image.size, 0)
493
+ draw = ImageDraw.Draw(mask)
494
+
495
+ # Создаем примерную маску в центре
496
+ width, height = image.size
497
+ margin = min(width, height) // 4
498
+
499
+ if "центр" in objects_text.lower() or "middle" in objects_text.lower():
500
+ draw.ellipse([margin, margin, width-margin, height-margin], fill=255)
501
+ elif "лев" in objects_text.lower() or "left" in objects_text.lower():
502
+ draw.rectangle([0, 0, width//2, height], fill=255)
503
+ elif "прав" in objects_text.lower() or "right" in objects_text.lower():
504
+ draw.rectangle([width//2, 0, width, height], fill=255)
505
+ else:
506
+ # По умолчанию - центральная область
507
+ draw.ellipse([margin, margin, width-margin, height-margin], fill=255)
508
+
509
+ return mask, "✅ Маска создана. Для точного выделения используйте ручное рисование."
510
+
511
+ except Exception as e:
512
+ return None, f"❌ Ошибка: {str(e)}"
513
+
514
+ def suggest_styles(image):
515
+ """Предложение подходящих стилей"""
516
+ if image is None:
517
+ return "Загрузите изображение для получения рекомендаций"
518
+
519
+ room_type = processor.detect_room_type(image)
520
+
521
+ suggestions = f"""
522
+ ## 🏠 Анализ комнаты
523
+ Тип помещения: {room_type}
524
+
525
+ ### 🎨 Рекомендуемые стили:
526
+
527
+ 1. **Современный минимализм**
528
+ - Чистые линии и функциональность
529
+ - Нейтральные цвета с акцентами
530
+ - Идеально для: небольших пространств
531
+
532
+ 2. **Скандинавский**
533
+ - Уют и естественность (хюгге)
534
+ - Светлые тона и натуральные материалы
535
+ - Идеально для: северных комнат
536
+
537
+ 3. **Индустриальный**
538
+ - Брутальность и характер
539
+ - Металл, бетон, кирпич
540
+ - Идеально для: лофтов и студий
541
+
542
+ 4. **Бохо**
543
+ - Творчество и эклектика
544
+ - Яркие цвета и текстиль
545
+ - Идеально для: творческих личностей
546
+
547
+ ### 💡 Советы по настройкам:
548
+ - Интенсивность 50-70% - сохранит узнаваемость комнаты
549
+ - Интенсивность 70-90% - кардинальное преображение
550
+ - Режим Ultra - для финальной визуализации
551
+ - HDR освещение - добавит реализма
552
+
553
+ 🔒 {AUTHOR_INFO}
554
+ """
555
+ return suggestions
556
+
557
+ @spaces.GPU(duration=60)
558
+ def create_style_comparison(image, selected_styles, quality="balanced"):
559
+ """Создание сравнения стилей"""
560
+ if image is None:
561
+ return None, "❌ Загрузите изображение"
562
+
563
+ if not selected_styles or len(selected_styles) < 2:
564
+ return None, "❌ Выберите минимум 2 стиля для сравнения"
565
+
566
+ global designer
567
+ if designer is None:
568
+ designer = InteriorDesignerPro()
569
+
570
+ try:
571
+ room_type = processor.detect_room_type(image)
572
+ room_type_en = ROOM_TYPES.get(room_type, "living room")
573
+
574
+ # Генерируем изображения для каждого стиля
575
+ styled_images = []
576
+ for style in selected_styles[:6]: # Максимум 6 стилей
577
+ styled = designer.apply_style_pro(
578
+ image, style, room_type_en,
579
+ strength=0.75,
580
+ quality="fast" if quality == "balanced" else quality
581
+ )
582
+ styled = add_watermark(styled)
583
+ styled_images.append(styled)
584
+
585
+ # Создаем сетку сравнения
586
+ comparison_grid = designer._create_comparison_grid(
587
+ styled_images,
588
+ selected_styles[:6],
589
+ original=image
590
+ )
591
+
592
+ info = f"""
593
+ ✅ Сравнение стилей готово!
594
+ 📐 Тип комнаты: {room_type}
595
+ 🎨 Стили: {', '.join(selected_styles[:6])}
596
+ 🖼️ Количество вариантов: {len(styled_images)}
597
+ 🔒 {AUTHOR_INFO}
598
+ """
599
+
600
+ return comparison_grid, info
601
+
602
+ except Exception as e:
603
+ return None, f"❌ Ошибка: {str(e)}"
604
+
605
+ @spaces.GPU
606
+ def enhance_image(image, scale, dpi):
607
+ """Увеличение разрешения изображения"""
608
+ if image is None:
609
+ return None, None, "❌ Пожалуйста, загрузите изображение"
610
+
611
+ try:
612
+ enhancer = ImageEnhancer()
613
+
614
+ original_size = f"{image.width}×{image.height}"
615
+
616
+ # Улучшаем изображение
617
+ enhanced = enhancer.upscale(image, scale=scale)
618
+
619
+ # Проверяем, изменился ли размер
620
+ if enhanced.size == image.size:
621
+ new_width = image.width * scale
622
+ new_height = image.height * scale
623
+ enhanced = image.resize((new_width, new_height), Image.Resampling.LANCZOS)
624
+ method = "PIL LANCZOS"
625
+ else:
626
+ method = "Real-ESRGAN"
627
+
628
+ # Настраиваем DPI
629
+ enhanced = enhancer.set_dpi(enhanced, dpi)
630
+
631
+ # Добавляем водяной знак
632
+ enhanced = add_watermark(enhanced)
633
+
634
+ # Создаем сравнение
635
+ comparison = processor.create_before_after(image, enhanced)
636
+
637
+ enhanced_size = f"{enhanced.width}×{enhanced.height}"
638
+
639
+ info = f"""
640
+ ✅ Изображение увеличено!
641
+ 📐 Исходный размер: {original_size}
642
+ 📐 Новый размер: {enhanced_size}
643
+ 🔍 Масштаб: {scale}x
644
+ 📊 DPI: {dpi}
645
+ 🔧 Метод: {method}
646
+ 🔒 {AUTHOR_INFO}
647
+ """
648
+
649
+ return enhanced, comparison, info
650
+
651
+ except Exception as e:
652
+ try:
653
+ new_width = int(image.width * scale)
654
+ new_height = int(image.height * scale)
655
+ enhanced = image.resize((new_width, new_height), Image.Resampling.LANCZOS)
656
+ enhanced = add_watermark(enhanced)
657
+
658
+ info = f"""
659
+ ⚠️ Real-ESRGAN недоступен, использован PIL resize
660
+ 📐 Исходный размер: {image.width}×{image.height}
661
+ 📐 Новый размер: {enhanced.width}×{enhanced.height}
662
+ 🔍 Масштаб: {scale}x
663
+ Ошибка: {str(e)}
664
+ """
665
+ return enhanced, None, info
666
+ except Exception as e2:
667
+ return None, None, f"❌ Ошибка увеличения: {str(e2)}"
668
+
669
+ # Создаем интерфейс
670
+ with gr.Blocks(title="AI Дизайнер интерьера Pro", theme=gr.themes.Soft(), css=custom_css) as app:
671
+ # Добавляем информацию об авторе вверху
672
+ gr.HTML(f"""
673
+ <div class="author-badge">
674
+ <h3>{AUTHOR_INFO}</h3>
675
+ <p>Protected AI Interior Designer Pro | Space ID: {SPACE_ID}</p>
676
+ </div>
677
+ """)
678
+
679
+ gr.Markdown("""
680
+ # 🎨 AI Дизайнер интерьера Pro
681
+ ### Преобразите свое пространство с помощью искусственного интеллекта
682
+ """)
683
+
684
+ with gr.Tabs():
685
+ # Основная вкладка
686
+ with gr.TabItem("🎨 Дизайнер", id=0):
687
+ with gr.Row():
688
+ with gr.Column(scale=1):
689
+ # Входные параметры
690
+ input_image = gr.Image(
691
+ label="Загрузите фото комнаты",
692
+ type="pil",
693
+ height=400
694
+ )
695
+
696
+ with gr.Accordion("🎨 Настройки дизайна", open=True):
697
+ style = gr.Dropdown(
698
+ label="Стиль интерьера",
699
+ choices=list(DESIGN_STYLES.keys()),
700
+ value="Современный минимализм"
701
+ )
702
+
703
+ room_type = gr.Dropdown(
704
+ label="Тип комнаты",
705
+ choices=["Автоопределение"] + list(ROOM_TYPES.keys()),
706
+ value="Автоопределение"
707
+ )
708
+
709
+ strength = gr.Slider(
710
+ label="Интенсивность изменений",
711
+ minimum=0.3,
712
+ maximum=0.95,
713
+ value=0.75,
714
+ step=0.05,
715
+ info="Насколько сильно изменить оригинал"
716
+ )
717
+
718
+ quality_mode = gr.Radio(
719
+ label="Качество генерации",
720
+ choices=[
721
+ ("⚡ Быстрое (5 сек)", "fast"),
722
+ ("⚖️ Сбалансированное (15 сек)", "balanced"),
723
+ ("💎 Ультра (30 сек)", "ultra")
724
+ ],
725
+ value="balanced",
726
+ info="Выберите баланс между скоростью и качеством"
727
+ )
728
+
729
+ with gr.Row():
730
+ enhance_lighting = gr.Checkbox(
731
+ label="✨ HDR освещение",
732
+ value=True
733
+ )
734
+ add_details = gr.Checkbox(
735
+ label="🔍 Улучшить детализацию",
736
+ value=True
737
+ )
738
+ use_variations = gr.Checkbox(
739
+ label="🎭 Создать вариации",
740
+ value=False
741
+ )
742
+
743
+ with gr.Accordion("🎯 Продвинутые настройки", open=False):
744
+ custom_prompt = gr.Textbox(
745
+ label="Кастомное описание (английский)",
746
+ placeholder="cozy room with warm colors, plants, natural light",
747
+ lines=3,
748
+ info="Опишите желаемый результат своими словами"
749
+ )
750
+
751
+ negative_prompt = gr.Textbox(
752
+ label="Чего избегать",
753
+ placeholder="dark, cluttered, old furniture",
754
+ lines=2
755
+ )
756
+
757
+ process_btn = gr.Button(
758
+ "🎨 Преобразить интерьер",
759
+ variant="primary",
760
+ size="lg"
761
+ )
762
+
763
+ # Примеры
764
+ gr.Examples(
765
+ examples=[
766
+ ["examples/living_room.jpg", "Современный минимализм", "Гостиная", 0.75, "balanced"],
767
+ ["examples/bedroom.jpg", "Скандинавский", "Спальня", 0.7, "ultra"],
768
+ ["examples/kitchen.jpg", "Индустриальный", "Кухня", 0.8, "balanced"]
769
+ ],
770
+ inputs=[input_image, style, room_type, strength, quality_mode],
771
+ label="Примеры для быстрого старта"
772
+ )
773
+
774
+ with gr.Column(scale=1):
775
+ # Результаты
776
+ output_comparison = gr.Image(
777
+ label="Сравнение: До и После",
778
+ height=400
779
+ )
780
+
781
+ with gr.Row():
782
+ output_variations = gr.Image(
783
+ label="Варианты дизайна",
784
+ visible=False
785
+ )
786
+
787
+ output_palette = gr.Image(
788
+ label="Цветовая палитра",
789
+ height=100
790
+ )
791
+
792
+ output_info = gr.Markdown(
793
+ label="Информация о процессе"
794
+ )
795
+
796
+ # Вкладка удаления объектов с LAMA
797
+ with gr.TabItem("🗑️ Удаление объектов (LAMA)", id=1):
798
+ with gr.Row():
799
+ with gr.Column():
800
+ lama_image = gr.Image(
801
+ label="Загрузите фото",
802
+ type="pil",
803
+ height=400
804
+ )
805
+
806
+ gr.Markdown("""
807
+ ### 📝 Как удалить объекты:
808
+
809
+ **Вариант 1: Создайте маску вручную**
810
+ 1. Сохраните изображение выше
811
+ 2. Откройте в любом редакторе (Paint, Photoshop, GIMP)
812
+ 3. Закрасьте объекты БЕЛЫМ цветом
813
+ 4. Загрузите маску ниже
814
+
815
+ **Вариант 2: Автоматическая маска**
816
+ - Опишите объекты текстом ниже
817
+ """)
818
+
819
+ lama_mask = gr.Image(
820
+ label="Загрузите маску (белый = удалить, черный = оставить)",
821
+ type="pil",
822
+ height=400
823
+ )
824
+
825
+ with gr.Row():
826
+ auto_mask_text = gr.Textbox(
827
+ label="Или опишите объекты для удаления",
828
+ placeholder="мебель в центре, диван, стол",
829
+ lines=2
830
+ )
831
+
832
+ create_mask_btn = gr.Button("Создать маску автоматически")
833
+
834
+ lama_inpaint_prompt = gr.Textbox(
835
+ label="Чем заполнить пустоты (опционально)",
836
+ placeholder="empty floor, clean wall",
837
+ lines=2
838
+ )
839
+
840
+ remove_btn = gr.Button(
841
+ "🗑️ Удалить объекты",
842
+ variant="primary",
843
+ size="lg"
844
+ )
845
+
846
+ with gr.Column():
847
+ lama_output = gr.Image(
848
+ label="Результат",
849
+ height=400
850
+ )
851
+
852
+ lama_info = gr.Textbox(
853
+ label="Статус",
854
+ lines=3
855
+ )
856
+
857
+ # Примеры масок
858
+ gr.Markdown("""
859
+ ### 💡 Советы:
860
+ - Чем точнее маска, тем лучше результат
861
+ - Для мебели делайте маску чуть больше объекта
862
+ - LAMA сохраняет стиль комнаты, тол��ко убирает объекты
863
+ """)
864
+
865
+ # Остальные вкладки...
866
+ # Вкладка детальных изменений
867
+ with gr.TabItem("🔧 Детальные изменения", id=2):
868
+ with gr.Row():
869
+ with gr.Column():
870
+ detail_image = gr.Image(
871
+ label="Изображение для изменения",
872
+ type="pil",
873
+ height=300
874
+ )
875
+
876
+ element_type = gr.Dropdown(
877
+ label="Что изменить",
878
+ choices=list(ROOM_ELEMENTS.keys()),
879
+ value="Стены"
880
+ )
881
+
882
+ element_value = gr.Textbox(
883
+ label="Новое значение",
884
+ placeholder="Например: белый цвет, деревянный паркет",
885
+ lines=2
886
+ )
887
+
888
+ element_strength = gr.Slider(
889
+ label="Сила изменения",
890
+ minimum=0.3,
891
+ maximum=0.9,
892
+ value=0.5,
893
+ step=0.05
894
+ )
895
+
896
+ change_btn = gr.Button("Применить изменение", variant="primary")
897
+
898
+ with gr.Column():
899
+ detail_output = gr.Image(label="Результат", height=300)
900
+ detail_info = gr.Textbox(label="Статус", lines=2)
901
+
902
+ # Вкладка сравнения стилей
903
+ with gr.TabItem("🎭 Сравнение стилей", id=3):
904
+ with gr.Row():
905
+ with gr.Column():
906
+ compare_image = gr.Image(
907
+ label="Загрузите фото для сравнения стилей",
908
+ type="pil",
909
+ height=400
910
+ )
911
+
912
+ compare_styles = gr.CheckboxGroup(
913
+ label="Выберите стили для сравнения (от 2 до 6)",
914
+ choices=list(DESIGN_STYLES.keys()),
915
+ value=[]
916
+ )
917
+
918
+ compare_quality = gr.Radio(
919
+ label="Качество сравнения",
920
+ choices=[
921
+ ("Быстрое", "fast"),
922
+ ("Сбалансированное", "balanced")
923
+ ],
924
+ value="fast"
925
+ )
926
+
927
+ compare_btn = gr.Button(
928
+ "🎭 Сравнить стили",
929
+ variant="primary",
930
+ size="lg"
931
+ )
932
+
933
+ with gr.Column():
934
+ compare_output = gr.Image(
935
+ label="Сравнение стилей",
936
+ height=600
937
+ )
938
+
939
+ compare_info = gr.Markdown()
940
+
941
+ # Вкладка увеличения разрешения
942
+ with gr.TabItem("🔍 Увеличение разрешения", id=4):
943
+ with gr.Row():
944
+ with gr.Column():
945
+ upscale_image = gr.Image(
946
+ label="Загрузите изображение для увеличения",
947
+ type="pil",
948
+ height=400
949
+ )
950
+
951
+ upscale_factor = gr.Radio(
952
+ label="Масштаб увеличения",
953
+ choices=[("2x", 2), ("4x", 4)],
954
+ value=2
955
+ )
956
+
957
+ upscale_dpi = gr.Radio(
958
+ label="DPI для печати",
959
+ choices=[
960
+ ("Экран (96 DPI)", 96),
961
+ ("Документ (150 DPI)", 150),
962
+ ("Печать (300 DPI)", 300)
963
+ ],
964
+ value=150
965
+ )
966
+
967
+ upscale_btn = gr.Button(
968
+ "🔍 Увеличить разрешение",
969
+ variant="primary",
970
+ size="lg"
971
+ )
972
+
973
+ with gr.Column():
974
+ upscale_output = gr.Image(
975
+ label="Увеличенное изображение",
976
+ height=400
977
+ )
978
+
979
+ upscale_comparison = gr.Image(
980
+ label="Сравнение",
981
+ height=200
982
+ )
983
+
984
+ upscale_info = gr.Markdown()
985
+
986
+ # Вкладка рекомендаций
987
+ with gr.TabItem("💡 Рекомендации", id=5):
988
+ with gr.Row():
989
+ with gr.Column():
990
+ suggest_image = gr.Image(
991
+ label="Загрузите фото для анализа",
992
+ type="pil",
993
+ height=400
994
+ )
995
+
996
+ suggest_btn = gr.Button("Получить рекомендации", variant="primary")
997
+
998
+ with gr.Column():
999
+ suggestions = gr.Markdown()
1000
+
1001
+ # Цветовые палитры для стилей
1002
+ gr.Markdown("### 🎨 Цветовые палитры популярных стилей")
1003
+
1004
+ for style_name in list(DESIGN_STYLES.keys())[:4]:
1005
+ colors = ColorPalette.suggest_palette(style_name)
1006
+ color_blocks = " ".join([
1007
+ f'<span style="background-color:{c}; padding:10px 20px; margin:2px; display:inline-block; border-radius:5px;"></span>'
1008
+ for c in colors
1009
+ ])
1010
+ gr.HTML(f"<div class='info-box'><strong>{style_name}:</strong><br>{color_blocks}</div>")
1011
+
1012
+ # Обработчики событий
1013
+ process_btn.click(
1014
+ process_image,
1015
+ inputs=[input_image, style, room_type, strength, quality_mode,
1016
+ enhance_lighting, add_details, custom_prompt, use_variations, negative_prompt],
1017
+ outputs=[output_comparison, output_variations, output_palette, output_info]
1018
+ )
1019
+
1020
+ use_variations.change(
1021
+ lambda x: gr.update(visible=x),
1022
+ inputs=[use_variations],
1023
+ outputs=[output_variations]
1024
+ )
1025
+
1026
+ # LAMA handlers
1027
+ create_mask_btn.click(
1028
+ create_auto_mask,
1029
+ inputs=[lama_image, auto_mask_text],
1030
+ outputs=[lama_mask, lama_info]
1031
+ )
1032
+
1033
+ remove_btn.click(
1034
+ remove_objects_lama,
1035
+ inputs=[lama_image, lama_mask, lama_inpaint_prompt],
1036
+ outputs=[lama_output, lama_info]
1037
+ )
1038
+
1039
+ change_btn.click(
1040
+ change_room_element,
1041
+ inputs=[detail_image, element_type, element_value, element_strength],
1042
+ outputs=[detail_output, detail_info]
1043
+ )
1044
+
1045
+ compare_btn.click(
1046
+ create_style_comparison,
1047
+ inputs=[compare_image, compare_styles, compare_quality],
1048
+ outputs=[compare_output, compare_info]
1049
+ )
1050
+
1051
+ upscale_btn.click(
1052
+ enhance_image,
1053
+ inputs=[upscale_image, upscale_factor, upscale_dpi],
1054
+ outputs=[upscale_output, upscale_comparison, upscale_info]
1055
+ )
1056
+
1057
+ suggest_btn.click(
1058
+ suggest_styles,
1059
+ inputs=[suggest_image],
1060
+ outputs=[suggestions]
1061
+ )
1062
+
1063
+ # Информация внизу
1064
+ gr.Markdown(f"""
1065
+ ---
1066
+ ### 📝 Инструкция по использованию:
1067
+ 1. Загрузите фото вашей комнаты
1068
+ 2. Выберите стиль из 8 доступных вариантов
1069
+ 3. Настройте параметры по вкусу
1070
+ 4. Нажмите "Преобразить интерьер"
1071
+
1072
+ ### 🚀 Возможности:
1073
+ - 8 стилей дизайна - от минимализма до ар-деко
1074
+ - Автоопределение типа комнаты
1075
+ - Создание вариаций - до 8 вариантов за раз
1076
+ - Удаление объектов с LAMA
1077
+ - Детальные изменения - меняйте отдельные элементы
1078
+ - HDR освещение - профессиональная обработка света
1079
+ - Анализ цветовой палитры
1080
+
1081
+ ---
1082
+ <center>
1083
+ <div class="author-badge">
1084
+ <p>{AUTHOR_INFO}</p>
1085
+ <p>Made with ❤️ for interior design enthusiasts</p>
1086
+ </div>
1087
+ </center>
1088
+ """)
1089
+
1090
+ # ВАЖНО! Запуск приложения
1091
+ if __name__ == "__main__":
1092
+ app.launch(
1093
+ share=False,
1094
+ show_error=True,
1095
+ server_name="0.0.0.0",
1096
+ server_port=7860
1097
+ )