Update app.py
Browse files
app.py
CHANGED
@@ -12,364 +12,278 @@ import numpy as np
|
|
12 |
import cv2
|
13 |
from skimage import exposure
|
14 |
import torch
|
15 |
-
from dataclasses import dataclass
|
16 |
-
from enum import Enum
|
17 |
-
import textwrap
|
18 |
|
19 |
-
# Configuration
|
20 |
logging.basicConfig(level=logging.DEBUG)
|
21 |
logger = logging.getLogger(__name__)
|
22 |
load_dotenv()
|
23 |
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
prompt_prefix: str
|
37 |
-
negative_prompt: str
|
38 |
-
params: Dict[str, Any]
|
39 |
-
recommended_resolution: Tuple[int, int]
|
40 |
-
processing_steps: List[str]
|
41 |
-
|
42 |
-
ARTISTIC_STYLES = {
|
43 |
-
# Styles Traditionnels
|
44 |
-
"Renaissance": StyleDefinition(
|
45 |
-
name="Renaissance",
|
46 |
-
category=StyleCategory.TRADITIONAL,
|
47 |
-
prompt_prefix="renaissance style, anatomically correct, detailed texture, chiaroscuro lighting",
|
48 |
-
negative_prompt="modern, abstract, simplified",
|
49 |
-
params={
|
50 |
-
"guidance_scale": 9.0,
|
51 |
-
"num_inference_steps": 50,
|
52 |
-
"detail_level": 0.95,
|
53 |
-
"lighting_complexity": "high"
|
54 |
},
|
55 |
-
|
56 |
-
|
57 |
-
|
58 |
-
|
59 |
-
|
60 |
-
|
61 |
-
|
62 |
-
|
63 |
-
|
64 |
-
negative_prompt="natural, vintage, traditional",
|
65 |
-
params={
|
66 |
-
"saturation_boost": 1.9,
|
67 |
-
"neon_glow": True,
|
68 |
-
"volumetric_lighting": True
|
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 |
-
"processing_steps": ["glow_generation", "bloom_effect", "color_enhancement"]
|
95 |
-
},
|
96 |
-
# Autres effets textuels...
|
97 |
-
}
|
98 |
-
|
99 |
-
class QualityPreset(Enum):
|
100 |
-
DRAFT = {
|
101 |
-
"resolution_scale": 0.5,
|
102 |
-
"inference_steps": 20,
|
103 |
-
"guidance_scale": 7.0
|
104 |
-
}
|
105 |
-
STANDARD = {
|
106 |
-
"resolution_scale": 1.0,
|
107 |
-
"inference_steps": 50,
|
108 |
-
"guidance_scale": 7.5
|
109 |
-
}
|
110 |
-
PREMIUM = {
|
111 |
-
"resolution_scale": 2.0,
|
112 |
-
"inference_steps": 100,
|
113 |
-
"guidance_scale": 8.0
|
114 |
}
|
|
|
115 |
|
116 |
-
class
|
117 |
-
"""Processeur avancé pour le traitement d'images"""
|
118 |
-
|
119 |
def __init__(self):
|
120 |
-
self.
|
121 |
-
|
122 |
-
|
123 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
124 |
try:
|
125 |
-
|
126 |
-
|
127 |
-
|
128 |
-
image = processing_method(image, style.params)
|
129 |
-
|
130 |
-
# Optimisation finale selon le preset de qualité
|
131 |
-
image = self._apply_quality_optimization(image, quality.value)
|
132 |
|
133 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
134 |
|
|
|
135 |
except Exception as e:
|
136 |
-
logger.error(f"Erreur lors du
|
137 |
return image
|
138 |
-
|
139 |
-
def _apply_detail_enhancement(self, image: Image.Image, params: Dict) -> Image.Image:
|
140 |
-
"""Amélioration des détails avec contrôle précis"""
|
141 |
-
np_image = np.array(image)
|
142 |
-
|
143 |
-
# Application d'un filtre de netteté adaptatif
|
144 |
-
kernel_size = int(params.get("detail_level", 0.8) * 5)
|
145 |
-
kernel = np.ones((kernel_size, kernel_size), np.float32) / (kernel_size * kernel_size)
|
146 |
-
filtered = cv2.filter2D(np_image, -1, kernel)
|
147 |
-
|
148 |
-
return Image.fromarray(filtered)
|
149 |
-
|
150 |
-
def _apply_neon_enhancement(self, image: Image.Image, params: Dict) -> Image.Image:
|
151 |
-
"""Effets néon et lumineux avancés"""
|
152 |
-
# Conversion en HSV pour manipulation des couleurs
|
153 |
-
np_image = np.array(image)
|
154 |
-
hsv = cv2.cvtColor(np_image, cv2.COLOR_RGB2HSV)
|
155 |
-
|
156 |
-
# Augmentation de la saturation
|
157 |
-
hsv[..., 1] = hsv[..., 1] * params.get("saturation_boost", 1.5)
|
158 |
-
|
159 |
-
# Effet de bloom
|
160 |
-
if params.get("neon_glow"):
|
161 |
-
bloom_intensity = params.get("bloom_intensity", 0.3)
|
162 |
-
blur = cv2.GaussianBlur(hsv, (15, 15), 0)
|
163 |
-
hsv = cv2.addWeighted(hsv, 1, blur, bloom_intensity, 0)
|
164 |
-
|
165 |
-
# Reconversion en RGB
|
166 |
-
enhanced = cv2.cvtColor(hsv, cv2.COLOR_HSV2RGB)
|
167 |
-
return Image.fromarray(enhanced)
|
168 |
|
169 |
-
|
170 |
-
|
171 |
-
|
172 |
-
|
173 |
-
|
174 |
-
|
175 |
-
|
176 |
-
def _initialize_fonts(self) -> Dict[str, ImageFont.FreeTypeFont]:
|
177 |
-
"""Initialise les polices disponibles"""
|
178 |
-
fonts = {
|
179 |
-
"default": self.default_font,
|
180 |
-
}
|
181 |
-
|
182 |
-
# Tentative de chargement des polices système courantes
|
183 |
-
common_fonts = {
|
184 |
-
"arial": "/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf",
|
185 |
-
"times": "/usr/share/fonts/truetype/liberation/LiberationSerif-Regular.ttf",
|
186 |
-
"courier": "/usr/share/fonts/truetype/liberation/LiberationMono-Regular.ttf"
|
187 |
-
}
|
188 |
-
|
189 |
-
for name, path in common_fonts.items():
|
190 |
-
try:
|
191 |
-
fonts[name] = ImageFont.truetype(path, size=40)
|
192 |
-
except OSError:
|
193 |
-
logger.warning(f"Police {name} non trouvée, utilisation police par défaut")
|
194 |
-
fonts[name] = self.default_font
|
195 |
-
|
196 |
-
return fonts
|
197 |
-
|
198 |
-
def render_text(self, image: Image.Image, text: str, effect: str, position: Tuple[int, int], font_name: str = "default") -> Image.Image:
|
199 |
-
"""Application d'effets de texte avancés"""
|
200 |
-
effect_params = TEXT_EFFECTS[effect]
|
201 |
-
|
202 |
-
# Création d'un calque pour le texte
|
203 |
-
text_layer = Image.new('RGBA', image.size, (0, 0, 0, 0))
|
204 |
-
draw = ImageDraw.Draw(text_layer)
|
205 |
-
|
206 |
-
# Application des effets spécifiques
|
207 |
-
for step in effect_params.get("processing_steps", []):
|
208 |
-
text_layer = getattr(self, f"_apply_{step}")(text_layer, text, position, effect_params)
|
209 |
-
|
210 |
-
# Fusion avec l'image principale
|
211 |
-
return Image.alpha_composite(image.convert('RGBA'), text_layer)
|
212 |
-
|
213 |
-
def _apply_glow_generation(self, layer: Image.Image, text: str, position: Tuple[int, int], params: Dict) -> Image.Image:
|
214 |
-
"""Génération d'effets de lumière pour le texte"""
|
215 |
-
glow_intensity = params["render_params"].get("glow_intensity", 1.0)
|
216 |
-
|
217 |
-
# Création de plusieurs couches de glow
|
218 |
-
for radius in range(3, 15, 2):
|
219 |
-
glow_layer = layer.filter(ImageFilter.GaussianBlur(radius))
|
220 |
-
layer = Image.alpha_composite(layer, glow_layer)
|
221 |
-
|
222 |
-
return layer
|
223 |
|
224 |
-
|
225 |
-
|
226 |
-
|
227 |
-
|
228 |
-
|
229 |
-
|
230 |
-
|
231 |
-
|
232 |
-
|
233 |
-
|
234 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
235 |
def __init__(self):
|
236 |
-
self.
|
237 |
-
self.text_renderer = TextRenderer()
|
238 |
-
self.model_url = "https://api-inference.huggingface.co/models/stabilityai/stable-diffusion-xl-base-1.0"
|
239 |
self.headers = {"Authorization": f"Bearer {os.getenv('HUGGINGFACE_TOKEN')}"}
|
240 |
-
|
241 |
-
|
242 |
-
|
243 |
-
|
|
|
244 |
try:
|
245 |
-
# Récupération du style
|
246 |
-
|
247 |
-
if not style_def:
|
248 |
-
return None, "Style non reconnu"
|
249 |
-
|
250 |
-
# Construction du prompt optimisé
|
251 |
-
full_prompt = f"{style_def.prompt_prefix}, {prompt}"
|
252 |
|
|
|
|
|
|
|
253 |
# Paramètres de génération
|
254 |
-
|
255 |
-
"
|
256 |
-
"negative_prompt":
|
257 |
-
|
|
|
258 |
}
|
259 |
-
|
260 |
-
#
|
261 |
-
response = await self._generate_base_image(params)
|
262 |
-
if not response:
|
263 |
-
return None, "Échec de la génération"
|
264 |
-
|
265 |
-
# Traitement selon le style
|
266 |
-
processed_image = self.image_processor.process_image(response, style_def, quality)
|
267 |
-
|
268 |
-
# Ajout de texte si nécessaire
|
269 |
-
if text_params:
|
270 |
-
processed_image = self.text_renderer.render_text(
|
271 |
-
processed_image,
|
272 |
-
text_params["text"],
|
273 |
-
text_params["effect"],
|
274 |
-
text_params["position"]
|
275 |
-
)
|
276 |
-
|
277 |
-
return processed_image, "Génération réussie"
|
278 |
-
|
279 |
-
except Exception as e:
|
280 |
-
logger.error(f"Erreur lors de la génération: {str(e)}")
|
281 |
-
return None, f"Erreur: {str(e)}"
|
282 |
-
|
283 |
-
async def _generate_base_image(self, params: Dict) -> Optional[Image.Image]:
|
284 |
-
"""Génération de l'image base via API"""
|
285 |
-
try:
|
286 |
response = requests.post(
|
287 |
-
self.
|
288 |
headers=self.headers,
|
289 |
-
json=
|
290 |
timeout=30
|
291 |
)
|
292 |
-
|
293 |
if response.status_code != 200:
|
294 |
-
return None
|
295 |
-
|
296 |
-
|
297 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
298 |
except Exception as e:
|
299 |
-
logger.error(f"Erreur
|
300 |
-
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
301 |
|
302 |
def create_interface():
|
303 |
-
|
304 |
-
|
305 |
-
|
306 |
-
|
307 |
-
|
308 |
-
|
309 |
-
|
310 |
-
|
311 |
-
|
312 |
-
|
313 |
-
|
314 |
-
|
315 |
-
|
316 |
-
|
317 |
-
|
318 |
-
|
319 |
-
|
320 |
-
|
321 |
-
|
322 |
-
|
323 |
-
choices=[
|
324 |
-
label="Style Artistique"
|
325 |
-
)
|
326 |
-
|
327 |
-
quality = gr.Radio(
|
328 |
-
choices=[q.name for q in QualityPreset],
|
329 |
-
value="STANDARD",
|
330 |
-
label="Qualité"
|
331 |
-
)
|
332 |
-
|
333 |
-
with gr.Column():
|
334 |
-
text_input = gr.Textbox(
|
335 |
-
label="Texte à ajouter",
|
336 |
-
placeholder="Optionnel"
|
337 |
-
)
|
338 |
-
|
339 |
-
text_effect = gr.Dropdown(
|
340 |
-
choices=list(TEXT_EFFECTS.keys()),
|
341 |
-
label="Effet de Texte"
|
342 |
)
|
343 |
-
|
344 |
-
|
345 |
-
|
346 |
-
|
347 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
348 |
# Logique de génération
|
349 |
-
def
|
350 |
-
|
351 |
-
"
|
352 |
-
|
353 |
-
|
354 |
-
} if text else None
|
355 |
-
|
356 |
-
result, status_msg = engine.generate(
|
357 |
prompt=prompt,
|
358 |
-
|
359 |
-
|
360 |
-
|
|
|
361 |
)
|
362 |
|
363 |
-
return
|
364 |
-
|
365 |
generate_btn.click(
|
366 |
-
|
367 |
-
inputs=[prompt,
|
368 |
-
outputs=[image_output,
|
369 |
)
|
370 |
-
|
371 |
-
return
|
372 |
|
373 |
if __name__ == "__main__":
|
374 |
-
|
375 |
-
|
|
|
12 |
import cv2
|
13 |
from skimage import exposure
|
14 |
import torch
|
|
|
|
|
|
|
15 |
|
|
|
16 |
logging.basicConfig(level=logging.DEBUG)
|
17 |
logger = logging.getLogger(__name__)
|
18 |
load_dotenv()
|
19 |
|
20 |
+
# Constantes pour les styles disponibles
|
21 |
+
STYLE_CATEGORIES = {
|
22 |
+
"TRADITIONAL": {
|
23 |
+
"Renaissance": {
|
24 |
+
"prompt": "renaissance style masterpiece, anatomical precision, detailed texture, chiaroscuro lighting, oil painting technique, museum quality",
|
25 |
+
"negative_prompt": "modern, abstract, simple, flat, digital art",
|
26 |
+
"params": {
|
27 |
+
"guidance_scale": 9.0,
|
28 |
+
"num_inference_steps": 50,
|
29 |
+
"resolution": (4096, 4096),
|
30 |
+
"detail_threshold": 0.95,
|
31 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
32 |
},
|
33 |
+
"Impressionnisme": {
|
34 |
+
"prompt": "impressionist style, loose brushstrokes, natural light, vivid colors, en plein air painting",
|
35 |
+
"negative_prompt": "sharp details, digital art, modern",
|
36 |
+
"params": {
|
37 |
+
"guidance_scale": 7.5,
|
38 |
+
"num_inference_steps": 40,
|
39 |
+
"resolution": (2048, 2048),
|
40 |
+
"brush_simulation": True,
|
41 |
+
}
|
|
|
|
|
|
|
|
|
|
|
42 |
},
|
43 |
+
# ... autres styles traditionnels
|
44 |
+
},
|
|
|
45 |
|
46 |
+
"DIGITAL": {
|
47 |
+
"Cyberpunk": {
|
48 |
+
"prompt": "cyberpunk style, neon lights, volumetric fog, tech noir, high contrast, futuristic city",
|
49 |
+
"negative_prompt": "natural, vintage, traditional art",
|
50 |
+
"params": {
|
51 |
+
"guidance_scale": 8.0,
|
52 |
+
"neon_intensity": 1.5,
|
53 |
+
"volumetric_lighting": True,
|
54 |
+
"resolution": (3840, 2160),
|
55 |
+
}
|
56 |
},
|
57 |
+
"Holographique": {
|
58 |
+
"prompt": "holographic effect, iridescent colors, light refraction, transparent layers, futuristic",
|
59 |
+
"negative_prompt": "flat, matte, solid colors",
|
60 |
+
"params": {
|
61 |
+
"iridescence": 1.0,
|
62 |
+
"transparency": 0.7,
|
63 |
+
"resolution": (2560, 1440),
|
64 |
+
}
|
65 |
},
|
66 |
+
# ... autres styles numériques
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
67 |
}
|
68 |
+
}
|
69 |
|
70 |
+
class TextEffectProcessor:
|
|
|
|
|
71 |
def __init__(self):
|
72 |
+
self.effects = {
|
73 |
+
"Réaliste": self._realistic_text,
|
74 |
+
"Néon": self._neon_text,
|
75 |
+
"Holographique": self._holographic_text,
|
76 |
+
"3D": self._3d_text,
|
77 |
+
"Vintage": self._vintage_text,
|
78 |
+
"Graffiti": self._graffiti_text,
|
79 |
+
"Matrix": self._matrix_text
|
80 |
+
}
|
81 |
+
|
82 |
+
def apply_effect(self, image: Image.Image, text: str, effect: str, position: Tuple[int, int]) -> Image.Image:
|
83 |
+
if effect in self.effects:
|
84 |
+
return self.effects[effect](image, text, position)
|
85 |
+
return self._default_text(image, text, position)
|
86 |
+
|
87 |
+
def _realistic_text(self, image: Image.Image, text: str, position: Tuple[int, int]) -> Image.Image:
|
88 |
try:
|
89 |
+
draw = ImageDraw.Draw(image)
|
90 |
+
# Chargement d'une police par défaut
|
91 |
+
font = ImageFont.load_default()
|
|
|
|
|
|
|
|
|
92 |
|
93 |
+
# Effet d'ombre réaliste
|
94 |
+
shadow_offset = 2
|
95 |
+
# Dessiner l'ombre
|
96 |
+
draw.text((position[0] + shadow_offset, position[1] + shadow_offset),
|
97 |
+
text, font=font, fill=(0, 0, 0, 128))
|
98 |
+
# Dessiner le texte principal
|
99 |
+
draw.text(position, text, font=font, fill=(255, 255, 255))
|
100 |
|
101 |
+
return image
|
102 |
except Exception as e:
|
103 |
+
logger.error(f"Erreur lors du rendu réaliste: {str(e)}")
|
104 |
return image
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
105 |
|
106 |
+
def _neon_text(self, image: Image.Image, text: str, position: Tuple[int, int]) -> Image.Image:
|
107 |
+
try:
|
108 |
+
# Création d'un calque pour le texte néon
|
109 |
+
text_layer = Image.new('RGBA', image.size, (0, 0, 0, 0))
|
110 |
+
draw = ImageDraw.Draw(text_layer)
|
111 |
+
font = ImageFont.load_default()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
112 |
|
113 |
+
# Effet de glow
|
114 |
+
glow_colors = [(255, 182, 193), (255, 192, 203), (255, 202, 213)]
|
115 |
+
for i, color in enumerate(glow_colors):
|
116 |
+
offset = (3 - i) * 2
|
117 |
+
draw.text((position[0] - offset, position[1] - offset),
|
118 |
+
text, font=font, fill=color + (150,))
|
119 |
+
|
120 |
+
# Texte principal
|
121 |
+
draw.text(position, text, font=font, fill=(255, 255, 255, 255))
|
122 |
+
|
123 |
+
# Fusion des calques
|
124 |
+
return Image.alpha_composite(image.convert('RGBA'), text_layer)
|
125 |
+
except Exception as e:
|
126 |
+
logger.error(f"Erreur lors du rendu néon: {str(e)}")
|
127 |
+
return image
|
128 |
+
|
129 |
+
# ... autres méthodes d'effets de texte
|
130 |
+
|
131 |
+
class ImageGenerator:
|
132 |
def __init__(self):
|
133 |
+
self.api_url = "https://api-inference.huggingface.co/models/stabilityai/stable-diffusion-xl-base-1.0"
|
|
|
|
|
134 |
self.headers = {"Authorization": f"Bearer {os.getenv('HUGGINGFACE_TOKEN')}"}
|
135 |
+
self.text_processor = TextEffectProcessor()
|
136 |
+
|
137 |
+
async def generate_image(self, prompt: str, style_category: str, style_name: str,
|
138 |
+
text: Optional[str] = None, text_effect: Optional[str] = None,
|
139 |
+
position: Optional[Tuple[int, int]] = None) -> Tuple[Optional[Image.Image], str]:
|
140 |
try:
|
141 |
+
# Récupération des paramètres du style
|
142 |
+
style_info = STYLE_CATEGORIES[style_category][style_name]
|
|
|
|
|
|
|
|
|
|
|
143 |
|
144 |
+
# Construction du prompt final
|
145 |
+
final_prompt = f"{style_info['prompt']}, {prompt}"
|
146 |
+
|
147 |
# Paramètres de génération
|
148 |
+
generation_params = {
|
149 |
+
"inputs": final_prompt,
|
150 |
+
"negative_prompt": style_info["negative_prompt"],
|
151 |
+
"num_inference_steps": style_info["params"]["num_inference_steps"],
|
152 |
+
"guidance_scale": style_info["params"]["guidance_scale"],
|
153 |
}
|
154 |
+
|
155 |
+
# Appel à l'API
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
156 |
response = requests.post(
|
157 |
+
self.api_url,
|
158 |
headers=self.headers,
|
159 |
+
json=generation_params,
|
160 |
timeout=30
|
161 |
)
|
162 |
+
|
163 |
if response.status_code != 200:
|
164 |
+
return None, f"Erreur API: {response.status_code}"
|
165 |
+
|
166 |
+
# Traitement de l'image générée
|
167 |
+
image = Image.open(io.BytesIO(response.content))
|
168 |
+
|
169 |
+
# Application des effets de style spécifiques
|
170 |
+
image = self._apply_style_effects(image, style_info["params"])
|
171 |
+
|
172 |
+
# Ajout de texte si demandé
|
173 |
+
if text and text_effect:
|
174 |
+
image = self.text_processor.apply_effect(
|
175 |
+
image, text, text_effect,
|
176 |
+
position or (image.width//2, image.height//2)
|
177 |
+
)
|
178 |
+
|
179 |
+
return image, "Génération réussie!"
|
180 |
+
|
181 |
+
except Exception as e:
|
182 |
+
logger.error(f"Erreur lors de la génération: {str(e)}")
|
183 |
+
return None, f"Erreur: {str(e)}"
|
184 |
+
|
185 |
+
def _apply_style_effects(self, image: Image.Image, style_params: Dict) -> Image.Image:
|
186 |
+
"""Application des effets spécifiques au style"""
|
187 |
+
try:
|
188 |
+
# Conversion pour traitement
|
189 |
+
img_array = np.array(image)
|
190 |
+
|
191 |
+
# Application des effets selon les paramètres
|
192 |
+
if style_params.get("neon_intensity"):
|
193 |
+
img_array = self._apply_neon_effect(img_array, style_params["neon_intensity"])
|
194 |
+
|
195 |
+
if style_params.get("volumetric_lighting"):
|
196 |
+
img_array = self._apply_volumetric_lighting(img_array)
|
197 |
+
|
198 |
+
# Reconversion en image PIL
|
199 |
+
return Image.fromarray(img_array)
|
200 |
+
|
201 |
except Exception as e:
|
202 |
+
logger.error(f"Erreur lors de l'application des effets: {str(e)}")
|
203 |
+
return image
|
204 |
+
|
205 |
+
def _apply_neon_effect(self, img_array: np.ndarray, intensity: float) -> np.ndarray:
|
206 |
+
"""Applique un effet néon à l'image"""
|
207 |
+
# Conversion en HSV pour manipulation des couleurs
|
208 |
+
hsv = cv2.cvtColor(img_array, cv2.COLOR_RGB2HSV)
|
209 |
+
# Augmentation de la saturation
|
210 |
+
hsv[..., 1] = np.clip(hsv[..., 1] * intensity, 0, 255)
|
211 |
+
return cv2.cvtColor(hsv, cv2.COLOR_HSV2RGB)
|
212 |
+
|
213 |
+
def _apply_volumetric_lighting(self, img_array: np.ndarray) -> np.ndarray:
|
214 |
+
"""Ajoute un effet de lumière volumétrique"""
|
215 |
+
# Création d'un masque de luminosité
|
216 |
+
gray = cv2.cvtColor(img_array, cv2.COLOR_RGB2GRAY)
|
217 |
+
blur = cv2.GaussianBlur(gray, (0, 0), 15)
|
218 |
+
return cv2.addWeighted(img_array, 1, cv2.cvtColor(blur, cv2.COLOR_GRAY2RGB), 0.2, 0)
|
219 |
|
220 |
def create_interface():
|
221 |
+
generator = ImageGenerator()
|
222 |
+
|
223 |
+
with gr.Blocks() as demo:
|
224 |
+
gr.HTML("""<h1 style='text-align: center'>🎨 Equity Art Engine</h1>""")
|
225 |
+
|
226 |
+
with gr.Row():
|
227 |
+
with gr.Column(scale=1):
|
228 |
+
# Contrôles de génération
|
229 |
+
prompt = gr.Textbox(label="Description de l'image")
|
230 |
+
style_category = gr.Dropdown(
|
231 |
+
choices=list(STYLE_CATEGORIES.keys()),
|
232 |
+
label="Catégorie de Style"
|
233 |
+
)
|
234 |
+
style_name = gr.Dropdown(
|
235 |
+
label="Style Spécifique"
|
236 |
+
)
|
237 |
+
|
238 |
+
# Mise à jour dynamique des styles disponibles
|
239 |
+
def update_styles(category):
|
240 |
+
return gr.Dropdown.update(
|
241 |
+
choices=list(STYLE_CATEGORIES[category].keys())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
242 |
)
|
243 |
+
style_category.change(
|
244 |
+
update_styles,
|
245 |
+
inputs=[style_category],
|
246 |
+
outputs=[style_name]
|
247 |
+
)
|
248 |
+
|
249 |
+
# Contrôles de texte
|
250 |
+
text_input = gr.Textbox(label="Texte à ajouter (optionnel)")
|
251 |
+
text_effect = gr.Dropdown(
|
252 |
+
choices=["Réaliste", "Néon", "Holographique", "3D", "Vintage", "Graffiti", "Matrix"],
|
253 |
+
label="Effet de texte"
|
254 |
+
)
|
255 |
+
|
256 |
+
with gr.Column(scale=2):
|
257 |
+
# Zone de résultat
|
258 |
+
image_output = gr.Image(label="Image générée")
|
259 |
+
status_output = gr.Textbox(label="Status")
|
260 |
+
|
261 |
+
# Bouton de génération
|
262 |
+
generate_btn = gr.Button("Générer")
|
263 |
+
|
264 |
# Logique de génération
|
265 |
+
def generate(prompt, category, style, text, effect):
|
266 |
+
if not prompt or not category or not style:
|
267 |
+
return None, "Veuillez remplir tous les champs requis"
|
268 |
+
|
269 |
+
image, status = generator.generate_image(
|
|
|
|
|
|
|
270 |
prompt=prompt,
|
271 |
+
style_category=category,
|
272 |
+
style_name=style,
|
273 |
+
text=text if text else None,
|
274 |
+
text_effect=effect if text else None
|
275 |
)
|
276 |
|
277 |
+
return image, status
|
278 |
+
|
279 |
generate_btn.click(
|
280 |
+
generate,
|
281 |
+
inputs=[prompt, style_category, style_name, text_input, text_effect],
|
282 |
+
outputs=[image_output, status_output]
|
283 |
)
|
284 |
+
|
285 |
+
return demo
|
286 |
|
287 |
if __name__ == "__main__":
|
288 |
+
demo = create_interface()
|
289 |
+
demo.launch()
|