Equityone commited on
Commit
df2abe8
·
verified ·
1 Parent(s): ffa8ad6

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +270 -211
app.py CHANGED
@@ -1,283 +1,342 @@
1
  import gradio as gr
2
  import os
3
- from PIL import Image, ImageEnhance, ImageFilter
4
  import requests
5
  import io
6
  import gc
7
  import json
8
- from typing import Tuple, Optional, Dict, Any
9
  import logging
10
  from dotenv import load_dotenv
11
  import numpy as np
12
  import cv2
13
  from skimage import exposure
14
  import torch
 
 
 
15
 
16
- # Configuration du logging
17
  logging.basicConfig(level=logging.DEBUG)
18
  logger = logging.getLogger(__name__)
19
  load_dotenv()
20
 
21
- # Configurations photographiques inspirées d'appareils emblématiques
22
- PHOTO_STYLES = {
23
- "Leica": {
24
- "description": "Style Leica classique 35mm - Netteté exceptionnelle, contraste naturel",
25
- "params": {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
  "sharpness": 1.4,
27
- "contrast": 1.2,
28
- "black_point": 5,
29
- "white_point": 248,
30
- "color_balance": "neutral"
31
- }
32
  },
33
- "Hasselblad": {
34
- "description": "Qualité moyen format - Détails riches, tons subtils",
35
- "params": {
36
- "sharpness": 1.6,
37
- "contrast": 1.1,
38
- "dynamic_range": "extended",
39
- "color_depth": "high",
40
- "medium_format": True
41
- }
42
  },
43
- "Polaroid": {
44
- "description": "Look vintage instantané - Tons chauds, vignettage doux",
45
- "params": {
46
- "vintage_effect": True,
47
- "color_shift": "warm",
48
- "vignette": 0.3,
49
- "grain": "fine"
50
- }
51
- },
52
- "Fujifilm": {
53
- "description": "Rendu Fujifilm - Couleurs riches, tons chair naturels",
54
- "params": {
55
- "color_profile": "fuji",
56
- "skin_tones": "enhanced",
57
- "dynamic_range": "wide",
58
- "film_simulation": True
59
- }
60
- }
61
  }
62
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
  class ImageProcessor:
64
- """Processeur d'image avec simulations d'appareils photos classiques"""
65
 
66
  def __init__(self):
67
- self.device = "cuda" if torch.cuda.is_available() else "cpu"
68
-
69
- def apply_photo_style(self, image: Image.Image, style: str) -> Image.Image:
70
- """Applique un style photographique inspiré d'appareils classiques"""
71
- style_params = PHOTO_STYLES[style]["params"]
72
-
73
- # Création d'une copie de l'image pour le traitement
74
- processed = image.copy()
75
 
76
- # Application des paramètres de base
77
- if style_params.get("sharpness"):
78
- processed = ImageEnhance.Sharpness(processed).enhance(style_params["sharpness"])
79
-
80
- if style_params.get("contrast"):
81
- processed = ImageEnhance.Contrast(processed).enhance(style_params["contrast"])
82
-
83
- # Effets spéciaux selon le style
84
- if style_params.get("vintage_effect"):
85
- processed = self._apply_vintage_effect(processed)
86
 
87
- if style_params.get("medium_format"):
88
- processed = self._simulate_medium_format(processed)
89
 
90
- if style_params.get("vignette"):
91
- processed = self._add_vignette(processed, style_params["vignette"])
 
92
 
93
- return processed
94
-
95
- def _apply_vintage_effect(self, image: Image.Image) -> Image.Image:
96
- """Simule l'effet vintage type Polaroid"""
97
- # Conversion en numpy array pour le traitement
98
- img_array = np.array(image)
99
 
100
- # Ajustement des courbes de couleur
101
- img_array = exposure.adjust_gamma(img_array, 1.2)
 
 
102
 
103
- # Ajout de grain subtil
104
- noise = np.random.normal(0, 2, img_array.shape)
105
- img_array = np.clip(img_array + noise, 0, 255).astype(np.uint8)
106
 
107
- # Léger flou pour adoucir
108
- return Image.fromarray(img_array).filter(ImageFilter.GaussianBlur(0.5))
109
-
110
- def _simulate_medium_format(self, image: Image.Image) -> Image.Image:
111
- """Simule le rendu moyen format type Hasselblad"""
112
- # Augmentation de la netteté et du micro-contraste
113
- enhanced = ImageEnhance.Sharpness(image).enhance(1.6)
114
 
115
- # Optimisation de la plage dynamique
116
- img_array = np.array(enhanced)
117
- img_array = exposure.equalize_adapthist(img_array, clip_limit=0.02)
118
 
119
- return Image.fromarray((img_array * 255).astype(np.uint8))
 
 
 
 
 
 
 
 
 
 
 
120
 
121
- def _add_vignette(self, image: Image.Image, strength: float) -> Image.Image:
122
- """Ajoute un effet de vignettage"""
123
- # Création du masque de vignettage
124
- mask = Image.new('L', image.size, 255)
125
- draw = ImageDraw.Draw(mask)
126
 
127
- width, height = image.size
128
- for i in range(min(width, height) // 2):
129
- alpha = int(255 * (1 - i * strength / (min(width, height) // 2)))
130
- draw.ellipse([i, i, width-i, height-i], fill=alpha)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
131
 
132
- # Application du masque
133
- image.putalpha(mask)
134
- return image
135
 
136
- class ImageGenerator:
137
- """Générateur d'images avec confirmation et styles photographiques"""
138
 
139
  def __init__(self):
140
- self.API_URL = "https://api-inference.huggingface.co/models/stabilityai/stable-diffusion-xl-base-1.0"
141
- self.processor = ImageProcessor()
 
142
  self.headers = {"Authorization": f"Bearer {os.getenv('HUGGINGFACE_TOKEN')}"}
143
 
144
- async def confirm_generation(self, params: Dict[str, Any]) -> bool:
145
- """Demande confirmation avant génération"""
146
- style_info = PHOTO_STYLES[params["photo_style"]]
147
- confirmation_message = f"""
148
- 🎨 Paramètres de génération :
149
- - Style photo : {params['photo_style']} ({style_info['description']})
150
- - Description : {params['prompt']}
151
- - Format : {params['orientation']}
152
-
153
- Voulez-vous continuer ?
154
- """
155
- return await self._show_confirmation_dialog(confirmation_message)
156
-
157
- def generate(self, params: Dict[str, Any]) -> Tuple[Optional[Image.Image], str]:
158
- """Génération d'image avec style photographique"""
159
  try:
160
- if not await self.confirm_generation(params):
161
- return None, "Génération annulée par l'utilisateur"
 
 
 
 
 
162
 
163
- # Configuration de la requête
164
- generation_params = {
165
- "prompt": params["prompt"],
166
- "negative_prompt": "low quality, blurry, distorted",
167
- "num_inference_steps": 50,
168
- "guidance_scale": 7.5,
169
- "width": 1024 if params["orientation"] == "landscape" else 768,
170
- "height": 768 if params["orientation"] == "landscape" else 1024
171
  }
172
 
173
- # Génération via API
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
174
  response = requests.post(
175
- self.API_URL,
176
  headers=self.headers,
177
- json={"inputs": generation_params["prompt"], "parameters": generation_params},
178
  timeout=30
179
  )
180
 
181
  if response.status_code != 200:
182
- return None, f"Erreur API: {response.status_code}"
183
-
184
- # Traitement de l'image
185
- image = Image.open(io.BytesIO(response.content))
186
-
187
- # Application du style photographique
188
- processed_image = self.processor.apply_photo_style(
189
- image,
190
- params["photo_style"]
191
- )
192
-
193
- return processed_image, "✨ Génération réussie"
194
 
195
  except Exception as e:
196
- logger.error(f"Erreur lors de la génération: {str(e)}")
197
- return None, f"Erreur: {str(e)}"
198
- finally:
199
- gc.collect()
200
 
201
  def create_interface():
202
- """Création de l'interface utilisateur avec confirmation"""
203
- generator = ImageGenerator()
204
 
205
  with gr.Blocks(css="style.css") as app:
206
  gr.HTML("""
207
  <div class="header">
208
- <h1>🎨 Equity Art Studio Pro</h1>
209
- <p>Création d'images avec styles photographiques classiques</p>
210
  </div>
211
  """)
212
 
213
- with gr.Column():
214
- # Contrôles de base
215
- prompt = gr.Textbox(
216
- label="Description",
217
- placeholder="Décrivez l'image souhaitée..."
218
- )
219
-
220
- with gr.Row():
221
- orientation = gr.Radio(
222
- choices=["portrait", "landscape", "square"],
223
- value="portrait",
224
- label="Format"
225
- )
226
-
227
- photo_style = gr.Dropdown(
228
- choices=list(PHOTO_STYLES.keys()),
229
- value="Leica",
230
- label="Style Photographique"
231
- )
232
-
233
- # Description du style sélectionné
234
- style_info = gr.HTML()
235
-
236
- def update_style_info(style):
237
- return f"<div class='style-info'>{PHOTO_STYLES[style]['description']}</div>"
238
-
239
- photo_style.change(update_style_info, inputs=[photo_style], outputs=[style_info])
240
-
241
- # Boutons
242
  with gr.Row():
243
- generate_btn = gr.Button("🎨 Prévisualiser", variant="secondary")
244
- confirm_btn = gr.Button("✨ Générer", variant="primary")
245
-
246
- # Sorties
247
- preview = gr.Image(label="Prévisualisation", visible=False)
248
- result = gr.Image(label="Résultat")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
249
  status = gr.Textbox(label="Status")
250
 
251
- # Logique de génération
252
- def preview_params(prompt, orientation, style):
253
- return f"""
254
- <div class='preview'>
255
- <h3>Paramètres de génération</h3>
256
- <ul>
257
- <li>Description : {prompt}</li>
258
- <li>Format : {orientation}</li>
259
- <li>Style : {style}</li>
260
- </ul>
261
- <p>Confirmez pour lancer la génération.</p>
262
- </div>
263
- """
264
 
265
- generate_btn.click(
266
- preview_params,
267
- inputs=[prompt, orientation, photo_style],
268
- outputs=[preview]
 
269
  )
270
 
271
- confirm_btn.click(
272
- generator.generate,
273
- inputs={
274
- "prompt": prompt,
275
- "orientation": orientation,
276
- "photo_style": photo_style
277
- },
278
- outputs=[result, status]
279
- )
280
-
281
  return app
282
 
283
  if __name__ == "__main__":
 
1
  import gradio as gr
2
  import os
3
+ from PIL import Image, ImageDraw, ImageFont, ImageEnhance, ImageFilter
4
  import requests
5
  import io
6
  import gc
7
  import json
8
+ from typing import Tuple, Optional, Dict, Any, List
9
  import logging
10
  from dotenv import load_dotenv
11
  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
+ class StyleCategory(Enum):
25
+ TRADITIONAL = "Styles Traditionnels"
26
+ DIGITAL = "Rendus Numériques"
27
+ PHOTO = "Photographique"
28
+ ILLUSTRATION = "Illustration"
29
+ CONCEPT = "Conceptuel"
30
+ TYPOGRAPHY = "Typographie"
31
+
32
+ @dataclass
33
+ class StyleDefinition:
34
+ name: str
35
+ category: StyleCategory
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
+ recommended_resolution=(3840, 2160),
56
+ processing_steps=["detail_enhancement", "lighting_optimization", "texture_refinement"]
57
+ ),
58
+
59
+ # Rendus Numériques
60
+ "Cyberpunk": StyleDefinition(
61
+ name="Cyberpunk",
62
+ category=StyleCategory.DIGITAL,
63
+ prompt_prefix="cyberpunk style, neon lights, high tech, volumetric fog",
64
+ negative_prompt="natural, vintage, traditional",
65
+ params={
66
+ "saturation_boost": 1.9,
67
+ "neon_glow": True,
68
+ "volumetric_lighting": True
69
+ },
70
+ recommended_resolution=(2560, 1440),
71
+ processing_steps=["neon_enhancement", "volumetric_shading", "color_grading"]
72
+ ),
73
+
74
+ # Styles supplémentaires similaires...
75
+ }
76
+
77
+ TEXT_EFFECTS = {
78
+ "Réaliste": {
79
+ "render_params": {
80
  "sharpness": 1.4,
81
+ "antialiasing": True,
82
+ "realistic_shadows": True
83
+ },
84
+ "font_requirements": ["high_resolution", "professional"],
85
+ "processing_steps": ["shadow_mapping", "texture_overlay"]
86
  },
87
+ "Néon": {
88
+ "render_params": {
89
+ "glow_intensity": 1.5,
90
+ "bloom_radius": 10,
91
+ "color_vibrance": 1.8
92
+ },
93
+ "effects": ["outer_glow", "inner_glow", "light_rays"],
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 ImageProcessor:
117
+ """Processeur avancé pour le traitement d'images"""
118
 
119
  def __init__(self):
120
+ self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
 
 
 
 
 
 
 
121
 
122
+ def process_image(self, image: Image.Image, style: StyleDefinition, quality: QualityPreset) -> Image.Image:
123
+ """Pipeline complet de traitement d'image"""
124
+ try:
125
+ # Application des étapes de traitement définies dans le style
126
+ for step in style.processing_steps:
127
+ processing_method = getattr(self, f"_apply_{step}")
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
+ return image
 
134
 
135
+ except Exception as e:
136
+ logger.error(f"Erreur lors du traitement de l'image: {str(e)}")
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
+ class TextRenderer:
170
+ """Gestionnaire avancé de rendu de texte"""
171
 
172
+ def __init__(self):
173
+ self.available_fonts = self._load_fonts()
 
 
 
174
 
175
+ def render_text(self, image: Image.Image, text: str, effect: str, position: Tuple[int, int]) -> Image.Image:
176
+ """Application d'effets de texte avancés"""
177
+ effect_params = TEXT_EFFECTS[effect]
178
+
179
+ # Création d'un calque pour le texte
180
+ text_layer = Image.new('RGBA', image.size, (0, 0, 0, 0))
181
+ draw = ImageDraw.Draw(text_layer)
182
+
183
+ # Application des effets spécifiques
184
+ for step in effect_params.get("processing_steps", []):
185
+ text_layer = getattr(self, f"_apply_{step}")(text_layer, text, position, effect_params)
186
+
187
+ # Fusion avec l'image principale
188
+ return Image.alpha_composite(image.convert('RGBA'), text_layer)
189
+
190
+ def _apply_glow_generation(self, layer: Image.Image, text: str, position: Tuple[int, int], params: Dict) -> Image.Image:
191
+ """Génération d'effets de lumière pour le texte"""
192
+ glow_intensity = params["render_params"].get("glow_intensity", 1.0)
193
+
194
+ # Création de plusieurs couches de glow
195
+ for radius in range(3, 15, 2):
196
+ glow_layer = layer.filter(ImageFilter.GaussianBlur(radius))
197
+ layer = Image.alpha_composite(layer, glow_layer)
198
 
199
+ return layer
 
 
200
 
201
+ class EquityArtEngine:
202
+ """Moteur principal de génération artistique"""
203
 
204
  def __init__(self):
205
+ self.image_processor = ImageProcessor()
206
+ self.text_renderer = TextRenderer()
207
+ self.model_url = "https://api-inference.huggingface.co/models/stabilityai/stable-diffusion-xl-base-1.0"
208
  self.headers = {"Authorization": f"Bearer {os.getenv('HUGGINGFACE_TOKEN')}"}
209
 
210
+ async def generate(self, prompt: str, style: str, quality: QualityPreset,
211
+ text_params: Optional[Dict] = None) -> Tuple[Optional[Image.Image], str]:
212
+ """Génération complète avec tous les effets"""
 
 
 
 
 
 
 
 
 
 
 
 
213
  try:
214
+ # Récupération du style et validation
215
+ style_def = ARTISTIC_STYLES.get(style)
216
+ if not style_def:
217
+ return None, "Style non reconnu"
218
+
219
+ # Construction du prompt optimisé
220
+ full_prompt = f"{style_def.prompt_prefix}, {prompt}"
221
 
222
+ # Paramètres de génération
223
+ params = {
224
+ "prompt": full_prompt,
225
+ "negative_prompt": style_def.negative_prompt,
226
+ **quality.value
 
 
 
227
  }
228
 
229
+ # Génération de l'image de base
230
+ response = await self._generate_base_image(params)
231
+ if not response:
232
+ return None, "Échec de la génération"
233
+
234
+ # Traitement selon le style
235
+ processed_image = self.image_processor.process_image(response, style_def, quality)
236
+
237
+ # Ajout de texte si nécessaire
238
+ if text_params:
239
+ processed_image = self.text_renderer.render_text(
240
+ processed_image,
241
+ text_params["text"],
242
+ text_params["effect"],
243
+ text_params["position"]
244
+ )
245
+
246
+ return processed_image, "Génération réussie"
247
+
248
+ except Exception as e:
249
+ logger.error(f"Erreur lors de la génération: {str(e)}")
250
+ return None, f"Erreur: {str(e)}"
251
+
252
+ async def _generate_base_image(self, params: Dict) -> Optional[Image.Image]:
253
+ """Génération de l'image base via API"""
254
+ try:
255
  response = requests.post(
256
+ self.model_url,
257
  headers=self.headers,
258
+ json={"inputs": params["prompt"], "parameters": params},
259
  timeout=30
260
  )
261
 
262
  if response.status_code != 200:
263
+ return None
264
+
265
+ return Image.open(io.BytesIO(response.content))
 
 
 
 
 
 
 
 
 
266
 
267
  except Exception as e:
268
+ logger.error(f"Erreur API: {str(e)}")
269
+ return None
 
 
270
 
271
  def create_interface():
272
+ """Interface utilisateur Gradio"""
273
+ engine = EquityArtEngine()
274
 
275
  with gr.Blocks(css="style.css") as app:
276
  gr.HTML("""
277
  <div class="header">
278
+ <h1>🎨 Equity Art Engine Pro</h1>
279
+ <p>Système de Génération Artistique Avancé</p>
280
  </div>
281
  """)
282
 
283
+ with gr.Tab("Création"):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
284
  with gr.Row():
285
+ with gr.Column():
286
+ prompt = gr.Textbox(
287
+ label="Description",
288
+ placeholder="Décrivez votre vision..."
289
+ )
290
+
291
+ style = gr.Dropdown(
292
+ choices=[s.name for s in ARTISTIC_STYLES.values()],
293
+ label="Style Artistique"
294
+ )
295
+
296
+ quality = gr.Radio(
297
+ choices=[q.name for q in QualityPreset],
298
+ value="STANDARD",
299
+ label="Qualité"
300
+ )
301
+
302
+ with gr.Column():
303
+ text_input = gr.Textbox(
304
+ label="Texte à ajouter",
305
+ placeholder="Optionnel"
306
+ )
307
+
308
+ text_effect = gr.Dropdown(
309
+ choices=list(TEXT_EFFECTS.keys()),
310
+ label="Effet de Texte"
311
+ )
312
+
313
+ generate_btn = gr.Button("Générer", variant="primary")
314
+ image_output = gr.Image(label="Résultat")
315
  status = gr.Textbox(label="Status")
316
 
317
+ # Logique de génération
318
+ def generate_artwork(prompt, style, quality, text, effect):
319
+ text_params = {
320
+ "text": text,
321
+ "effect": effect,
322
+ "position": (50, 50) # Position par défaut
323
+ } if text else None
 
 
 
 
 
 
324
 
325
+ result, status_msg = engine.generate(
326
+ prompt=prompt,
327
+ style=style,
328
+ quality=QualityPreset[quality],
329
+ text_params=text_params
330
  )
331
 
332
+ return result, status_msg
333
+
334
+ generate_btn.click(
335
+ generate_artwork,
336
+ inputs=[prompt, style, quality, text_input, text_effect],
337
+ outputs=[image_output, status]
338
+ )
339
+
 
 
340
  return app
341
 
342
  if __name__ == "__main__":