|
import gradio as gr |
|
import os |
|
from PIL import Image, ImageDraw, ImageFont, ImageEnhance |
|
import requests |
|
import io |
|
import json |
|
|
|
|
|
|
|
ART_STYLES = { |
|
"Art Moderne": { |
|
"prompt_prefix": "modern art style poster, professional design", |
|
"text_style": "modern clean typography, geometric style", |
|
"guidance": 9.0, |
|
"steps": 55, |
|
"negative_prompt": "traditional, photorealistic, cluttered, busy design" |
|
}, |
|
"Neo Vintage": { |
|
"prompt_prefix": "vintage style advertising poster, retro design", |
|
"text_style": "retro typography, vintage lettering", |
|
"guidance": 8.5, |
|
"steps": 50, |
|
"negative_prompt": "modern, digital, contemporary style" |
|
}, |
|
"Pop Art": { |
|
"prompt_prefix": "pop art style poster, bold design", |
|
"text_style": "bold typography, comic book style text", |
|
"guidance": 8.0, |
|
"steps": 45, |
|
"negative_prompt": "subtle, realistic, traditional art" |
|
}, |
|
"Minimaliste": { |
|
"prompt_prefix": "minimalist design poster, clean composition", |
|
"text_style": "clean minimal typography, simple text layout", |
|
"guidance": 7.5, |
|
"steps": 40, |
|
"negative_prompt": "complex, detailed, ornate, busy" |
|
} |
|
} |
|
ART_STYLES = { |
|
|
|
"Photo HDR": { |
|
"prompt_prefix": "ultra realistic photograph, professional HDR photography, extremely detailed, 8k uhd", |
|
"text_style": "photographic text overlay", |
|
"guidance": 9.0, |
|
"steps": 60, |
|
"negative_prompt": "illustration, painting, drawing, cartoon, blurry, low quality, artistic" |
|
}, |
|
"Portrait Studio": { |
|
"prompt_prefix": "professional studio photography, high end photoshoot, perfect lighting, sharp focus", |
|
"text_style": "elegant text overlay, magazine style typography", |
|
"guidance": 8.5, |
|
"steps": 55, |
|
"negative_prompt": "illustration, drawing, cartoon, painting, low quality" |
|
}, |
|
"Nature Pro": { |
|
"prompt_prefix": "professional nature photography, national geographic style, perfect natural lighting", |
|
"text_style": "outdoor photography text style", |
|
"guidance": 8.0, |
|
"steps": 50, |
|
"negative_prompt": "illustration, artificial, cartoon, painting" |
|
}, |
|
"Urban Photo": { |
|
"prompt_prefix": "professional urban photography, architectural photo, perfect city shot", |
|
"text_style": "modern photographic typography", |
|
"guidance": 8.5, |
|
"steps": 55, |
|
"negative_prompt": "illustration, drawing, cartoon, unrealistic" |
|
}, |
|
|
|
|
|
"Art Moderne": { |
|
"prompt_prefix": "modern art style poster, professional design", |
|
"text_style": "modern clean typography, geometric style", |
|
"guidance": 9.0, |
|
"steps": 55, |
|
"negative_prompt": "photorealistic, traditional, cluttered" |
|
}, |
|
"Neo Vintage": { |
|
"prompt_prefix": "vintage style advertising poster, retro design", |
|
"text_style": "retro typography, vintage lettering", |
|
"guidance": 8.5, |
|
"steps": 50, |
|
"negative_prompt": "modern, photographic, contemporary" |
|
}, |
|
|
|
} |
|
|
|
|
|
PHOTO_PARAMS = { |
|
"HDR": { |
|
"exposure_strength": 1.2, |
|
"contrast": 1.1, |
|
"saturation": 1.05 |
|
}, |
|
"Portrait": { |
|
"skin_softening": 0.8, |
|
"bokeh_strength": 0.7, |
|
"lighting": "studio" |
|
}, |
|
"Nature": { |
|
"sharpness": 1.2, |
|
"vibrance": 1.1, |
|
"detail_enhancement": 1.15 |
|
} |
|
} |
|
|
|
def enhance_photo_prompt(subject, style, photo_type="HDR"): |
|
"""Améliore le prompt pour un rendu plus photoréaliste""" |
|
base_prompt = f"highly detailed photograph of {subject}, {ART_STYLES[style]['prompt_prefix']}" |
|
|
|
photo_enhancements = { |
|
"HDR": ", ultra high resolution photograph, perfect exposure, dramatic lighting, professional color grading", |
|
"Portrait": ", professional portrait photography, perfect skin texture, studio lighting setup, shallow depth of field", |
|
"Nature": ", sharp nature photography, perfect natural lighting, high detail macro shot, professional outdoor photography", |
|
"Urban": ", professional architectural photography, perfect perspective, golden hour lighting, urban landscape" |
|
} |
|
|
|
return base_prompt + photo_enhancements.get(photo_type, photo_enhancements["HDR"]) |
|
|
|
def generate_image(format_size, orientation, subject, style, text_effect, collection, title, subtitle, quality, creativity): |
|
API_URL = "https://api-inference.huggingface.co/models/stabilityai/stable-diffusion-xl-base-1.0" |
|
headers = {"Authorization": f"Bearer {os.getenv('HUGGINGFACE_TOKEN')}"} |
|
|
|
try: |
|
width, height = (768, 1024) if format_size == "A4" else (1024, 1024) |
|
if orientation == "Paysage": |
|
width, height = height, width |
|
|
|
|
|
is_photo_style = any(photo_style in style for photo_style in ["Photo", "Portrait", "Nature Pro", "Urban Photo"]) |
|
|
|
if is_photo_style: |
|
prompt = enhance_photo_prompt(subject, style) |
|
guidance_scale = 8.0 |
|
steps = 55 |
|
else: |
|
prompt = f"{ART_STYLES[style]['prompt_prefix']}, {subject}" |
|
guidance_scale = ART_STYLES[style]['guidance'] |
|
steps = ART_STYLES[style]['steps'] |
|
|
|
if text_effect != "Standard": |
|
prompt += f", {TEXT_EFFECTS[text_effect]['prompt_suffix']}" |
|
|
|
if title: |
|
prompt += f", with text '{title}'" |
|
|
|
payload = { |
|
"inputs": prompt, |
|
"parameters": { |
|
"negative_prompt": ART_STYLES[style]['negative_prompt'], |
|
"num_inference_steps": min(int(steps * (quality/100)), 60), |
|
"guidance_scale": min(guidance_scale * (creativity/10), 10.0), |
|
"width": width, |
|
"height": height |
|
} |
|
} |
|
|
|
print(f"Prompt: {prompt}") |
|
print(f"Paramètres: {payload}") |
|
|
|
response = requests.post(API_URL, headers=headers, json=payload, timeout=30) |
|
|
|
if response.status_code == 200: |
|
image = Image.open(io.BytesIO(response.content)) |
|
|
|
|
|
if is_photo_style: |
|
image = enhance_photo(image, style) |
|
|
|
return image, "✨ Photo créée avec succès!" |
|
else: |
|
return None, f"⚠️ Erreur {response.status_code}: Essayez de modifier les paramètres" |
|
|
|
except Exception as e: |
|
print(f"Exception: {str(e)}") |
|
return None, f"⚠️ Erreur: {str(e)}" |
|
|
|
def enhance_photo(image, style): |
|
"""Améliore la photo selon le style""" |
|
try: |
|
enhancer = ImageEnhance.Contrast(image) |
|
image = enhancer.enhance(1.1) |
|
|
|
enhancer = ImageEnhance.Color(image) |
|
image = enhancer.enhance(1.05) |
|
|
|
enhancer = ImageEnhance.Sharpness(image) |
|
image = enhancer.enhance(1.15) |
|
|
|
return image |
|
except: |
|
return image |
|
|
|
TEXT_EFFECTS = { |
|
"Standard": { |
|
"prompt_suffix": "with clear readable text", |
|
"text_params": {"weight": 1.0} |
|
}, |
|
"Graffiti": { |
|
"prompt_suffix": "with urban graffiti style text", |
|
"text_params": {"weight": 0.8} |
|
}, |
|
"Néon": { |
|
"prompt_suffix": "with glowing neon text", |
|
"text_params": {"weight": 0.9} |
|
}, |
|
"3D": { |
|
"prompt_suffix": "with 3D style text", |
|
"text_params": {"weight": 0.85} |
|
} |
|
} |
|
|
|
|
|
THEME_COLLECTIONS = { |
|
"Tech": { |
|
"prompts": ["technology inspired", "digital aesthetic"], |
|
"styles": ["modern tech elements", "futuristic design"], |
|
"negative": "organic, rustic, natural" |
|
}, |
|
"Nature": { |
|
"prompts": ["natural elements", "organic design"], |
|
"styles": ["flowing shapes", "natural textures"], |
|
"negative": "artificial, geometric" |
|
}, |
|
"Urbain": { |
|
"prompts": ["urban style", "city aesthetic"], |
|
"styles": ["street art influence", "urban textures"], |
|
"negative": "rural, natural" |
|
} |
|
} |
|
|
|
def generate_image(format_size, orientation, subject, style, text_effect, collection, title, subtitle, quality, creativity): |
|
API_URL = "https://api-inference.huggingface.co/models/stabilityai/stable-diffusion-xl-base-1.0" |
|
headers = {"Authorization": f"Bearer {os.getenv('HUGGINGFACE_TOKEN')}"} |
|
|
|
try: |
|
|
|
width, height = (768, 1024) if format_size == "A4" else (1024, 1024) |
|
if orientation == "Paysage": |
|
width, height = height, width |
|
|
|
|
|
style_config = ART_STYLES[style] |
|
text_config = TEXT_EFFECTS[text_effect] |
|
|
|
prompt = f"{style_config['prompt_prefix']}, {subject}" |
|
|
|
|
|
if text_effect != "Standard": |
|
prompt += f", {text_config['prompt_suffix']}" |
|
|
|
if collection and collection in THEME_COLLECTIONS: |
|
coll = THEME_COLLECTIONS[collection] |
|
prompt += f", {', '.join(coll['prompts'][:1])}" |
|
|
|
if title: |
|
prompt += f", with text '{title}'" |
|
|
|
|
|
payload = { |
|
"inputs": prompt, |
|
"parameters": { |
|
"negative_prompt": style_config['negative_prompt'], |
|
"num_inference_steps": min(style_config['steps'], 50), |
|
"guidance_scale": min(style_config['guidance'] * (creativity/10), 10.0), |
|
"width": width, |
|
"height": height |
|
} |
|
} |
|
|
|
print(f"Prompt: {prompt}") |
|
print(f"Paramètres: {payload}") |
|
|
|
response = requests.post(API_URL, headers=headers, json=payload, timeout=30) |
|
|
|
if response.status_code == 200: |
|
image = Image.open(io.BytesIO(response.content)) |
|
return image, "✨ Création réussie!" |
|
else: |
|
print(f"Erreur API: {response.text}") |
|
return None, f"⚠️ Erreur {response.status_code}: Essayez de modifier les paramètres" |
|
|
|
except Exception as e: |
|
print(f"Exception: {str(e)}") |
|
return None, f"⚠️ Erreur: {str(e)}" |
|
|
|
|
|
TEXT_EFFECTS = { |
|
"Standard": { |
|
"prompt_suffix": "with clear readable text", |
|
"text_params": {"weight": 1.0} |
|
}, |
|
"Néon": { |
|
"prompt_suffix": "with glowing neon text effect", |
|
"text_params": {"weight": 1.2} |
|
}, |
|
"3D": { |
|
"prompt_suffix": "with 3D text effect, depth and shadows", |
|
"text_params": {"weight": 1.3} |
|
}, |
|
"Métallique": { |
|
"prompt_suffix": "with metallic text effect, reflective surface", |
|
"text_params": {"weight": 1.1} |
|
}, |
|
"Graffiti": { |
|
"prompt_suffix": "with graffiti style text, urban art typography", |
|
"text_params": {"weight": 1.2} |
|
} |
|
} |
|
|
|
|
|
THEME_COLLECTIONS = { |
|
"Nature": { |
|
"prompts": ["natural elements", "organic composition"], |
|
"styles": ["flowing lines", "natural textures"], |
|
"negative": "artificial, synthetic" |
|
}, |
|
"Urbain": { |
|
"prompts": ["urban landscape", "city elements"], |
|
"styles": ["street art", "architectural elements"], |
|
"negative": "rural, natural" |
|
}, |
|
"Tech": { |
|
"prompts": ["technological elements", "digital aesthetic"], |
|
"styles": ["circuit patterns", "tech elements"], |
|
"negative": "organic, traditional" |
|
} |
|
} |
|
|
|
|
|
CUSTOM_CSS = """ |
|
.container { max-width: 1200px; margin: auto; } |
|
.welcome { text-align: center; margin: 20px 0; padding: 20px; background: #1e293b; border-radius: 10px; } |
|
.quality-controls { margin: 10px 0; padding: 10px; background: #2d3748; border-radius: 5px; } |
|
.style-group { background: #2d3748; padding: 15px; border-radius: 5px; margin: 10px 0; } |
|
.text-effects { background: #374151; padding: 12px; border-radius: 5px; margin: 8px 0; } |
|
.preview-panel { position: relative; } |
|
.parameters { display: flex; gap: 10px; } |
|
""" |
|
|
|
def enhance_prompt(subject, style, text_effect, collection=None, additional_details=""): |
|
"""Génération de prompt optimisée""" |
|
style_config = ART_STYLES[style] |
|
text_config = TEXT_EFFECTS[text_effect] |
|
|
|
base_prompt = f"{style_config['prompt_prefix']}, {subject}" |
|
|
|
if text_effect != "Standard": |
|
base_prompt += f", {text_config['prompt_suffix']}" |
|
|
|
if collection and collection in THEME_COLLECTIONS: |
|
collection_data = THEME_COLLECTIONS[collection] |
|
collection_elements = collection_data["prompts"] + collection_data["styles"] |
|
base_prompt += f", {', '.join(collection_elements)}" |
|
|
|
if additional_details: |
|
base_prompt += f", {additional_details}" |
|
|
|
negative_prompt = style_config['negative_prompt'] |
|
if collection: |
|
negative_prompt += f", {THEME_COLLECTIONS[collection]['negative']}" |
|
|
|
return base_prompt, negative_prompt |
|
|
|
def generate_image(format_size, orientation, subject, style, text_effect, collection, |
|
title, subtitle, quality, creativity, additional_details=""): |
|
"""Fonction de génération d'image""" |
|
API_URL = "https://api-inference.huggingface.co/models/stabilityai/stable-diffusion-xl-base-1.0" |
|
headers = {"Authorization": f"Bearer {os.getenv('HUGGINGFACE_TOKEN')}"} |
|
|
|
try: |
|
base_width = 1024 |
|
base_height = 1024 |
|
if orientation == "Portrait": |
|
if format_size in ["A4", "A3"]: |
|
base_width = 768 |
|
base_height = 1024 |
|
else: |
|
if format_size in ["A4", "A3"]: |
|
base_width = 1024 |
|
base_height = 768 |
|
|
|
enhanced_prompt, negative_prompt = enhance_prompt( |
|
subject, style, text_effect, collection, additional_details |
|
) |
|
|
|
if title: |
|
enhanced_prompt += f", with text: '{title}'" |
|
if subtitle: |
|
enhanced_prompt += f", subtitle: '{subtitle}'" |
|
|
|
print(f"Prompt: {enhanced_prompt}") |
|
|
|
payload = { |
|
"inputs": enhanced_prompt, |
|
"parameters": { |
|
"negative_prompt": negative_prompt, |
|
"num_inference_steps": int(ART_STYLES[style]['steps'] * (quality/100)), |
|
"guidance_scale": ART_STYLES[style]['guidance'] * (creativity/10), |
|
"width": base_width, |
|
"height": base_height |
|
} |
|
} |
|
|
|
response = requests.post(API_URL, headers=headers, json=payload, timeout=60) |
|
|
|
if response.status_code == 200: |
|
image = Image.open(io.BytesIO(response.content)) |
|
return image, f"✨ Création {style} avec effet {text_effect} réussie!" |
|
else: |
|
print(f"Erreur API: {response.text}") |
|
return None, f"⚠️ Erreur {response.status_code}: Ajustez les paramètres" |
|
|
|
except Exception as e: |
|
print(f"Exception: {str(e)}") |
|
return None, f"⚠️ Erreur: {str(e)}" |
|
|
|
def create_interface(): |
|
with gr.Blocks(css=CUSTOM_CSS) as app: |
|
gr.HTML(""" |
|
<div class="welcome"> |
|
<h1>🤖 Equity Artisan 3.0</h1> |
|
<p>Créez des affiches artistiques professionnelles avec notre assistant créatif augmenté.</p> |
|
</div> |
|
""") |
|
|
|
with gr.Column(elem_classes="container"): |
|
|
|
with gr.Group(elem_classes="style-group"): |
|
gr.Markdown("### 📏 Format et Orientation") |
|
with gr.Row(): |
|
format_choice = gr.Dropdown( |
|
choices=["A4", "A3", "A2", "A1", "A0"], |
|
value="A4", |
|
label="Format" |
|
) |
|
orientation = gr.Radio( |
|
choices=["Portrait", "Paysage"], |
|
value="Portrait", |
|
label="Orientation" |
|
) |
|
|
|
|
|
with gr.Group(elem_classes="style-group"): |
|
gr.Markdown("### 🎨 Style et Création") |
|
with gr.Row(): |
|
with gr.Column(scale=1): |
|
style = gr.Dropdown( |
|
choices=list(ART_STYLES.keys()), |
|
value="Neo Vintage", |
|
label="Style artistique" |
|
) |
|
text_effect = gr.Dropdown( |
|
choices=list(TEXT_EFFECTS.keys()), |
|
value="Standard", |
|
label="Effet de texte" |
|
) |
|
collection = gr.Dropdown( |
|
choices=list(THEME_COLLECTIONS.keys()), |
|
label="Collection (optionnel)" |
|
) |
|
|
|
with gr.Column(scale=2): |
|
subject = gr.Textbox( |
|
label="Sujet principal", |
|
placeholder="Ex: loup, paysage urbain..." |
|
) |
|
additional_details = gr.Textbox( |
|
lines=2, |
|
label="Détails additionnels", |
|
placeholder="Ajoutez des détails..." |
|
) |
|
|
|
|
|
with gr.Group(elem_classes="text-effects"): |
|
gr.Markdown("### ✍️ Texte") |
|
with gr.Row(): |
|
title = gr.Textbox( |
|
label="Titre principal", |
|
placeholder="Texte principal..." |
|
) |
|
subtitle = gr.Textbox( |
|
label="Sous-titre", |
|
placeholder="Texte secondaire..." |
|
) |
|
|
|
|
|
with gr.Group(elem_classes="quality-controls"): |
|
with gr.Row(): |
|
quality = gr.Slider( |
|
minimum=30, |
|
maximum=50, |
|
value=35, |
|
step=5, |
|
label="Qualité" |
|
) |
|
creativity = gr.Slider( |
|
minimum=5, |
|
maximum=15, |
|
value=7.5, |
|
step=0.5, |
|
label="Créativité" |
|
) |
|
|
|
with gr.Row(): |
|
generate_btn = gr.Button("✨ Générer", variant="primary") |
|
clear_btn = gr.Button("🗑️ Effacer", variant="secondary") |
|
|
|
image_output = gr.Image(label="Aperçu", type="pil") |
|
status = gr.Textbox(label="Statut", interactive=False) |
|
|
|
|
|
generate_btn.click( |
|
generate_image, |
|
inputs=[ |
|
format_choice, |
|
orientation, |
|
subject, |
|
style, |
|
text_effect, |
|
collection, |
|
title, |
|
subtitle, |
|
quality, |
|
creativity, |
|
additional_details |
|
], |
|
outputs=[image_output, status] |
|
) |
|
|
|
clear_btn.click( |
|
lambda: (None, "Image effacée"), |
|
outputs=[image_output, status] |
|
) |
|
|
|
return app |
|
|
|
if __name__ == "__main__": |
|
app = create_interface() |
|
app.launch() |