Equityone commited on
Commit
f8c3466
·
verified ·
1 Parent(s): 9bd137d

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +191 -164
app.py CHANGED
@@ -17,268 +17,295 @@ 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
 
 
17
  logger = logging.getLogger(__name__)
18
  load_dotenv()
19
 
20
+ # Définition complète des styles artistiques
21
+ ART_STYLES = {
22
+ "Styles Traditionnels": {
23
  "Renaissance": {
24
+ "prompt": "renaissance masterpiece, anatomical precision, high detail texture quality, chiaroscuro lighting, oil painting, 4K resolution, museum quality, classical art",
25
+ "negative_prompt": "modern, abstract, simple, digital",
26
  "params": {
27
+ "pixel_detail": 0.95,
 
28
  "resolution": (4096, 4096),
29
+ "guidance_scale": 9.0,
30
+ "steps": 50
31
  }
32
  },
33
  "Impressionnisme": {
34
+ "prompt": "impressionist style painting, visible brushstrokes, natural light effects, plein air scene, vibrant colors, monet-like technique",
35
+ "negative_prompt": "sharp details, photorealistic, digital",
36
  "params": {
37
+ "noise_strength": 0.3,
 
38
  "resolution": (2048, 2048),
39
+ "guidance_scale": 7.5,
40
+ "steps": 40
41
  }
42
  },
43
+ "Surréalisme": {
44
+ "prompt": "surrealist dreamlike scene, dream-like quality, unconscious imagination, unexpected juxtapositions",
45
+ "negative_prompt": "realistic, ordinary, conventional",
46
+ "params": {
47
+ "randomization": 0.3,
48
+ "guidance_scale": 8.0,
49
+ "steps": 45
50
+ }
51
+ }
52
  },
53
+ "Rendus Numériques": {
 
54
  "Cyberpunk": {
55
+ "prompt": "cyberpunk style, neon lights, volumetric fog, high tech, dynamic lighting, futuristic cityscape",
56
+ "negative_prompt": "natural, vintage, traditional",
57
  "params": {
58
+ "saturation": 1.9,
59
+ "neon_strength": 1.5,
60
+ "volumetric": True
 
61
  }
62
  },
63
+ "Synthwave": {
64
+ "prompt": "synthwave aesthetic, retro-futuristic, neon grid, 80s style, dramatic lighting",
65
+ "negative_prompt": "realistic, modern, natural",
66
+ "params": {
67
+ "saturation": 1.8,
68
+ "neon_strength": 1.4,
69
+ "lut_intensity": 0.9
70
+ }
71
+ }
72
+ },
73
+ "Photographie": {
74
+ "HDR": {
75
+ "prompt": "HDR photography, extreme dynamic range, rich details in shadows and highlights, 8K quality",
76
+ "negative_prompt": "flat lighting, low contrast",
77
+ "params": {
78
+ "dynamic_range": 1.5,
79
+ "resolution": (7680, 4320),
80
+ "exposure_levels": 3
81
+ }
82
+ },
83
+ "Portrait Studio": {
84
+ "prompt": "professional studio portrait photography, bokeh effect, controlled lighting, sharp focus on subject",
85
+ "negative_prompt": "blurry, noisy, low quality",
86
+ "params": {
87
+ "bokeh_strength": 0.7,
88
+ "sharpness": 1.4,
89
+ "focus_area": 0.5
90
+ }
91
+ }
92
+ },
93
+ "Art Moderne": {
94
+ "Flat Design": {
95
+ "prompt": "flat design style, minimal, clean shapes, solid colors, modern aesthetic",
96
+ "negative_prompt": "detailed, textured, realistic",
97
  "params": {
98
+ "simplification": 0.8,
99
+ "resolution": (1920, 1080),
100
+ "color_reduction": True
101
  }
102
  },
103
+ "3D Isométrique": {
104
+ "prompt": "isometric 3D design, clean geometric shapes, precise angles, modern visualization",
105
+ "negative_prompt": "realistic perspective, organic shapes",
106
+ "params": {
107
+ "geometric_precision": 1.0,
108
+ "angle_snap": 30,
109
+ "shading": "flat"
110
+ }
111
+ }
112
  }
113
  }
114
 
115
+ class TextProcessor:
116
+ """Processeur de texte avec effets de base"""
117
+
118
  def __init__(self):
119
+ self.font = ImageFont.load_default()
120
+
121
+ def add_text(self, image: Image.Image, text: str, position: Tuple[int, int]) -> Image.Image:
122
+ """Ajoute du texte basique à l'image"""
 
 
 
 
 
 
 
 
 
 
 
 
123
  try:
124
  draw = ImageDraw.Draw(image)
125
+ # Texte simple avec ombre
126
+ shadow_color = "black"
127
+ text_color = "white"
128
 
129
+ # Dessine l'ombre
130
+ draw.text((position[0]+2, position[1]+2), text,
131
+ font=self.font, fill=shadow_color)
132
+ # Dessine le texte
133
+ draw.text(position, text, font=self.font, fill=text_color)
 
 
134
 
135
  return image
136
  except Exception as e:
137
+ logger.error(f"Erreur lors de l'ajout de texte: {str(e)}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
138
  return image
139
 
 
 
140
  class ImageGenerator:
141
+ """Générateur d'images avec styles artistiques"""
142
+
143
  def __init__(self):
144
  self.api_url = "https://api-inference.huggingface.co/models/stabilityai/stable-diffusion-xl-base-1.0"
145
+ token = os.getenv('HUGGINGFACE_TOKEN')
146
+ if not token:
147
+ logger.error("HUGGINGFACE_TOKEN non trouvé!")
148
+ self.headers = {"Authorization": f"Bearer {token}"}
149
+ self.text_processor = TextProcessor()
150
+
151
+ def generate(self, prompt: str, style_category: str, style_name: str,
152
+ text: Optional[str] = None) -> Tuple[Optional[Image.Image], str]:
153
  try:
154
+ # Récupération du style
155
+ style_info = ART_STYLES[style_category][style_name]
156
 
157
  # Construction du prompt final
158
+ final_prompt = f"{prompt}, {style_info['prompt']}"
159
+
160
  # Paramètres de génération
161
+ params = {
162
  "inputs": final_prompt,
163
  "negative_prompt": style_info["negative_prompt"],
164
+ "guidance_scale": style_info["params"].get("guidance_scale", 7.5),
165
+ "num_inference_steps": style_info["params"].get("steps", 50),
166
  }
167
+
168
+ # Appel API
169
  response = requests.post(
170
  self.api_url,
171
  headers=self.headers,
172
+ json=params,
173
  timeout=30
174
  )
175
+
176
  if response.status_code != 200:
177
+ logger.error(f"Erreur API: {response.status_code}")
178
  return None, f"Erreur API: {response.status_code}"
179
+
180
+ # Traitement de l'image
181
  image = Image.open(io.BytesIO(response.content))
182
+
183
+ # Application des effets de style
184
  image = self._apply_style_effects(image, style_info["params"])
185
+
186
+ # Ajout de texte si nécessaire
187
+ if text:
188
+ image = self.text_processor.add_text(
189
+ image,
190
+ text,
191
+ (image.width//2, image.height//2)
192
  )
193
+
194
+ return image, "Génération réussie!"
195
+
196
  except Exception as e:
197
+ logger.error(f"Erreur de génération: {str(e)}")
198
  return None, f"Erreur: {str(e)}"
199
+ finally:
200
+ gc.collect()
201
 
202
  def _apply_style_effects(self, image: Image.Image, style_params: Dict) -> Image.Image:
203
+ """Applique les effets spécifiques au style"""
204
  try:
205
  # Conversion pour traitement
206
  img_array = np.array(image)
207
+
208
  # Application des effets selon les paramètres
209
+ if style_params.get("saturation"):
210
+ img_array = self._adjust_saturation(img_array, style_params["saturation"])
211
+
212
+ if style_params.get("neon_strength"):
213
+ img_array = self._apply_neon_effect(img_array, style_params["neon_strength"])
214
+
215
+ if style_params.get("volumetric"):
216
+ img_array = self._add_volumetric_effect(img_array)
217
+
218
+ if style_params.get("bokeh_strength"):
219
+ img_array = self._apply_bokeh(img_array, style_params["bokeh_strength"])
220
+
221
  return Image.fromarray(img_array)
222
+
223
  except Exception as e:
224
  logger.error(f"Erreur lors de l'application des effets: {str(e)}")
225
  return image
226
 
227
+ def _adjust_saturation(self, image: np.ndarray, factor: float) -> np.ndarray:
228
+ hsv = cv2.cvtColor(image, cv2.COLOR_RGB2HSV)
229
+ hsv[..., 1] = np.clip(hsv[..., 1] * factor, 0, 255)
 
 
 
230
  return cv2.cvtColor(hsv, cv2.COLOR_HSV2RGB)
231
 
232
+ def _apply_neon_effect(self, image: np.ndarray, strength: float) -> np.ndarray:
233
+ blurred = cv2.GaussianBlur(image, (0, 0), 15)
234
+ return cv2.addWeighted(image, 1, blurred, strength, 0)
235
+
236
+ def _add_volumetric_effect(self, image: np.ndarray) -> np.ndarray:
237
+ gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)
238
+ fog = cv2.GaussianBlur(gray, (0, 0), 20)
239
+ return cv2.addWeighted(image, 1, cv2.cvtColor(fog, cv2.COLOR_GRAY2RGB), 0.2, 0)
240
+
241
+ def _apply_bokeh(self, image: np.ndarray, strength: float) -> np.ndarray:
242
+ blurred = cv2.GaussianBlur(image, (0, 0), int(30 * strength))
243
+ mask = np.random.random(image.shape[:2]) > 0.5
244
+ result = image.copy()
245
+ result[mask] = blurred[mask]
246
+ return result
247
 
248
  def create_interface():
249
+ """Création de l'interface utilisateur"""
250
  generator = ImageGenerator()
251
 
252
  with gr.Blocks() as demo:
253
+ gr.HTML("<h1>🎨 Equity Art Engine Pro</h1>")
254
+
255
  with gr.Row():
256
+ with gr.Column():
257
+ # Contrôles principaux
258
  prompt = gr.Textbox(label="Description de l'image")
259
+
260
  style_category = gr.Dropdown(
261
+ choices=list(ART_STYLES.keys()),
262
  label="Catégorie de Style"
263
  )
264
+
265
  style_name = gr.Dropdown(
266
  label="Style Spécifique"
267
  )
268
 
269
+ # Mise à jour dynamique des styles
270
  def update_styles(category):
271
  return gr.Dropdown.update(
272
+ choices=list(ART_STYLES[category].keys()) if category else []
273
  )
274
+
275
  style_category.change(
276
  update_styles,
277
  inputs=[style_category],
278
  outputs=[style_name]
279
  )
280
+
281
+ text_input = gr.Textbox(
282
+ label="Texte à ajouter (optionnel)",
283
+ placeholder="Laissez vide pour une image sans texte"
 
 
284
  )
285
+
286
+ generate_btn = gr.Button("✨ Générer")
287
+
288
+ with gr.Column():
289
+ # Affichage
290
+ image_output = gr.Image(label="Image Générée")
291
  status_output = gr.Textbox(label="Status")
 
 
 
292
 
293
+ def generate_image(prompt, category, style, text):
 
294
  if not prompt or not category or not style:
295
+ return None, "⚠️ Veuillez remplir tous les champs requis"
296
+
297
+ image, status = generator.generate(
298
  prompt=prompt,
299
  style_category=category,
300
  style_name=style,
301
+ text=text if text else None
 
302
  )
303
 
304
  return image, status
305
+
306
  generate_btn.click(
307
+ generate_image,
308
+ inputs=[prompt, style_category, style_name, text_input],
309
  outputs=[image_output, status_output]
310
  )
311