Equityone commited on
Commit
6ae236a
·
verified ·
1 Parent(s): f8c3466

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +161 -223
app.py CHANGED
@@ -17,298 +17,236 @@ logging.basicConfig(level=logging.DEBUG)
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
-
312
  return demo
313
 
314
  if __name__ == "__main__":
 
17
  logger = logging.getLogger(__name__)
18
  load_dotenv()
19
 
20
+ def load_art_styles():
21
+ return {
22
+ "Styles Traditionnels": {
23
+ "Renaissance": {"prompt": "renaissance masterpiece, anatomical precision, detailed texture, chiaroscuro lighting", "params": {"resolution": (4096, 4096), "detail_level": 0.95}},
24
+ "Impressionnisme": {"prompt": "impressionist style painting, visible brushstrokes, natural light", "params": {"resolution": (3072, 3072), "noise_level": 0.3}},
25
+ "Surréalisme": {"prompt": "surrealist dreamlike scene, symbolic elements, subconscious imagery", "params": {"randomization": 0.4}},
26
+ "Cubisme": {"prompt": "cubist style, geometric forms, multiple perspectives", "params": {"geometric_strength": 0.8}}
 
 
 
 
 
27
  },
28
+ "Rendus Numériques": {
29
+ "Synthwave": {"prompt": "synthwave aesthetic, neon grid, retro-futuristic, vibrant", "params": {"saturation": 1.8, "neon": True}},
30
+ "Cyberpunk": {"prompt": "cyberpunk style, neon-lit, high-tech, volumetric lighting", "params": {"volumetric": True, "neon": True}},
31
+ "Sci-Fi": {"prompt": "sci-fi environment, futuristic technology, advanced architecture", "params": {"tech_level": 0.9}}
 
 
 
 
 
32
  },
33
+ "Photographie": {
34
+ "HDR": {"prompt": "HDR photography, extreme dynamic range, detailed shadows and highlights", "params": {"hdr_strength": 1.5}},
35
+ "Macro": {"prompt": "macro photography, extreme close-up, fine details", "params": {"detail_scale": 0.5}},
36
+ "Portrait": {"prompt": "professional portrait photography, studio lighting, bokeh", "params": {"bokeh": 0.7}},
37
+ "Vintage": {"prompt": "vintage photography, aged effect, retro colors", "params": {"grain": 0.4}}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
  },
39
+ "Illustration": {
40
+ "Aquarelle": {"prompt": "watercolor illustration, fluid transparency, soft edges", "params": {"transparency": 0.6}},
41
+ "Encre": {"prompt": "ink drawing, bold strokes, high contrast", "params": {"contrast": 1.4}},
42
+ "Huile": {"prompt": "oil painting, thick impasto, rich colors", "params": {"texture": 0.8}}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
  },
44
+ "Photoréalisme": {
45
+ "Nature_Morte": {"prompt": "photorealistic still life, extreme detail, perfect lighting", "params": {"detail_level": 0.98}},
46
+ "Paysage": {"prompt": "photorealistic landscape, natural lighting, atmospheric", "params": {"atmosphere": 0.7}}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47
  },
48
+ "Fantasy": {
49
+ "Fantasy": {"prompt": "fantasy art, magical atmosphere, mythical elements", "params": {"magic_effect": 0.8}},
50
+ "Dark_Fantasy": {"prompt": "dark fantasy, gothic elements, mysterious atmosphere", "params": {"darkness": 0.7}},
51
+ "Steampunk": {"prompt": "steampunk style, brass and copper, mechanical elements", "params": {"mechanical": 0.9}}
52
+ },
53
+ "Abstrait": {
54
+ "Holographique": {"prompt": "holographic effect, iridescent colors, light refraction", "params": {"iridescence": 0.8}},
55
+ "Fractal": {"prompt": "fractal art, recursive patterns, mathematical beauty", "params": {"complexity": 0.9}}
56
+ },
57
+ "Graphisme": {
58
+ "Flat": {"prompt": "flat design, minimal shapes, solid colors", "params": {"simplification": 0.8}},
59
+ "Material": {"prompt": "material design, subtle shadows, layered elements", "params": {"layers": 0.6}},
60
+ "Isométrique": {"prompt": "isometric design, geometric precision, clean lines", "params": {"precision": 0.9}}
61
+ },
62
+ "Gaming": {
63
+ "Pixel_Art": {"prompt": "pixel art style, retro gaming aesthetic, limited palette", "params": {"pixelation": 0.7}},
64
+ "Cel_Shading": {"prompt": "cel shaded style, anime-like, bold outlines", "params": {"outline": 0.8}}
65
  }
66
  }
 
67
 
68
+ class ImageProcessor:
 
 
69
  def __init__(self):
70
+ self.effects = {
71
+ "neon": self._apply_neon,
72
+ "bokeh": self._apply_bokeh,
73
+ "grain": self._apply_grain,
74
+ "hdr": self._apply_hdr,
75
+ "pixelation": self._apply_pixelation
76
+ }
77
+
78
+ def process_image(self, image: Image.Image, style_params: Dict) -> Image.Image:
79
  try:
80
+ img_array = np.array(image)
 
 
 
 
 
 
 
 
 
81
 
82
+ for effect, value in style_params.items():
83
+ if effect in self.effects and value:
84
+ img_array = self.effects[effect](img_array, value)
85
+
86
+ return Image.fromarray(img_array)
87
  except Exception as e:
88
+ logger.error(f"Erreur traitement: {str(e)}")
89
  return image
90
 
91
+ def _apply_neon(self, image: np.ndarray, strength: float) -> np.ndarray:
92
+ hsv = cv2.cvtColor(image, cv2.COLOR_RGB2HSV)
93
+ hsv[..., 1] = np.clip(hsv[..., 1] * strength, 0, 255)
94
+ glow = cv2.GaussianBlur(cv2.cvtColor(hsv, cv2.COLOR_HSV2RGB), (0, 0), 15)
95
+ return cv2.addWeighted(image, 1, glow, 0.5, 0)
96
+
97
+ def _apply_bokeh(self, image: np.ndarray, strength: float) -> np.ndarray:
98
+ blur = cv2.GaussianBlur(image, (0, 0), int(30 * strength))
99
+ mask = np.random.random(image.shape[:2]) > 0.5
100
+ result = image.copy()
101
+ result[mask] = blur[mask]
102
+ return result
103
+
104
+ def _apply_grain(self, image: np.ndarray, strength: float) -> np.ndarray:
105
+ noise = np.random.normal(0, strength * 50, image.shape).astype(np.uint8)
106
+ return np.clip(image + noise, 0, 255)
107
+
108
+ def _apply_hdr(self, image: np.ndarray, strength: float) -> np.ndarray:
109
+ return exposure.adjust_gamma(image, 1.0 / strength)
110
+
111
+ def _apply_pixelation(self, image: np.ndarray, strength: float) -> np.ndarray:
112
+ h, w = image.shape[:2]
113
+ size = int(max(h, w) * (1 - strength))
114
+ small = cv2.resize(image, (size, size), interpolation=cv2.INTER_LINEAR)
115
+ return cv2.resize(small, (w, h), interpolation=cv2.INTER_NEAREST)
116
+
117
  class ImageGenerator:
 
 
118
  def __init__(self):
119
+ self.processor = ImageProcessor()
120
  self.api_url = "https://api-inference.huggingface.co/models/stabilityai/stable-diffusion-xl-base-1.0"
121
+ self.headers = {"Authorization": f"Bearer {os.getenv('HUGGINGFACE_TOKEN')}"}
122
+ self.styles = load_art_styles()
 
 
 
123
 
124
+ async def generate(self, prompt: str, style_category: str, style_name: str) -> Tuple[Optional[Image.Image], str]:
 
125
  try:
126
+ style_info = self.styles[style_category][style_name]
 
 
 
127
  final_prompt = f"{prompt}, {style_info['prompt']}"
128
 
 
 
 
 
 
 
 
 
 
129
  response = requests.post(
130
  self.api_url,
131
  headers=self.headers,
132
+ json={"inputs": final_prompt},
133
  timeout=30
134
  )
135
 
136
  if response.status_code != 200:
 
137
  return None, f"Erreur API: {response.status_code}"
138
+
 
139
  image = Image.open(io.BytesIO(response.content))
140
+ processed = self.processor.process_image(image, style_info['params'])
141
 
142
+ return processed, "✨ Génération réussie!"
 
 
 
 
 
 
 
 
 
 
 
 
143
  except Exception as e:
144
+ logger.error(f"Erreur: {str(e)}")
145
  return None, f"Erreur: {str(e)}"
146
  finally:
147
+ gc.collect()def create_interface():
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
148
  generator = ImageGenerator()
149
 
150
  with gr.Blocks() as demo:
151
+ gr.HTML("""
152
+ <div style='text-align: center; margin-bottom: 1rem'>
153
+ <h1>🎨 Equity Art Engine</h1>
154
+ <p>Générateur d'Images Avancé avec Styles Artistiques</p>
155
+ </div>
156
+ """)
157
 
158
  with gr.Row():
159
+ with gr.Column(scale=1):
160
+ prompt = gr.Textbox(
161
+ label="Description",
162
+ placeholder="Décrivez votre image..."
163
+ )
164
 
165
  style_category = gr.Dropdown(
166
+ choices=list(generator.styles.keys()),
167
+ label="Catégorie de Style",
168
+ value="Styles Traditionnels"
169
  )
170
 
171
  style_name = gr.Dropdown(
172
  label="Style Spécifique"
173
  )
174
 
 
175
  def update_styles(category):
176
  return gr.Dropdown.update(
177
+ choices=list(generator.styles[category].keys()) if category else []
178
  )
179
 
180
  style_category.change(
181
  update_styles,
182
+ inputs=style_category,
183
+ outputs=style_name
184
  )
185
 
186
+ advanced_params = gr.Checkbox(
187
+ label="Paramètres avancés",
188
+ value=False
189
  )
190
 
191
+ with gr.Column(visible=False) as advanced_options:
192
+ quality = gr.Slider(
193
+ minimum=1,
194
+ maximum=10,
195
+ value=7,
196
+ step=1,
197
+ label="Qualité"
198
+ )
199
+
200
+ seed = gr.Number(
201
+ label="Seed (-1 pour aléatoire)",
202
+ value=-1
203
+ )
204
 
205
+ def toggle_advanced(show):
206
+ return gr.update(visible=show)
207
+
208
+ advanced_params.change(
209
+ toggle_advanced,
210
+ inputs=advanced_params,
211
+ outputs=advanced_options
212
+ )
213
+
214
+ generate_btn = gr.Button("✨ Générer", variant="primary")
215
+
216
+ with gr.Column(scale=2):
217
+ output_image = gr.Image(label="Image Générée")
218
+ status = gr.Textbox(label="Status")
219
 
220
+ def generate_image(prompt, category, style, use_advanced, quality, seed):
221
+ if not all([prompt, category, style]):
222
  return None, "⚠️ Veuillez remplir tous les champs requis"
223
 
224
+ params = {
225
+ "quality": quality if use_advanced else 7,
226
+ "seed": seed if use_advanced and seed != -1 else None
227
+ }
228
+
229
+ image, status_msg = generator.generate(
230
  prompt=prompt,
231
  style_category=category,
232
+ style_name=style
 
233
  )
234
 
235
+ return image, status_msg
236
 
237
  generate_btn.click(
238
  generate_image,
239
+ inputs=[
240
+ prompt,
241
+ style_category,
242
+ style_name,
243
+ advanced_params,
244
+ quality,
245
+ seed
246
+ ],
247
+ outputs=[output_image, status]
248
  )
249
+
250
  return demo
251
 
252
  if __name__ == "__main__":