|
import gradio as gr |
|
import os |
|
from PIL import Image, ImageDraw, ImageFont, ImageEnhance, ImageFilter |
|
import requests |
|
import io |
|
import gc |
|
import json |
|
from typing import Tuple, Optional, Dict, Any, List |
|
import logging |
|
from dotenv import load_dotenv |
|
import numpy as np |
|
import cv2 |
|
from skimage import exposure |
|
import torch |
|
|
|
logging.basicConfig(level=logging.DEBUG) |
|
logger = logging.getLogger(__name__) |
|
load_dotenv() |
|
|
|
|
|
STYLE_CATEGORIES = { |
|
"TRADITIONAL": { |
|
"Renaissance": { |
|
"prompt": "renaissance style masterpiece, anatomical precision, detailed texture, chiaroscuro lighting, oil painting technique, museum quality", |
|
"negative_prompt": "modern, abstract, simple, flat, digital art", |
|
"params": { |
|
"guidance_scale": 9.0, |
|
"num_inference_steps": 50, |
|
"resolution": (4096, 4096), |
|
"detail_threshold": 0.95, |
|
} |
|
}, |
|
"Impressionnisme": { |
|
"prompt": "impressionist style, loose brushstrokes, natural light, vivid colors, en plein air painting", |
|
"negative_prompt": "sharp details, digital art, modern", |
|
"params": { |
|
"guidance_scale": 7.5, |
|
"num_inference_steps": 40, |
|
"resolution": (2048, 2048), |
|
"brush_simulation": True, |
|
} |
|
}, |
|
|
|
}, |
|
|
|
"DIGITAL": { |
|
"Cyberpunk": { |
|
"prompt": "cyberpunk style, neon lights, volumetric fog, tech noir, high contrast, futuristic city", |
|
"negative_prompt": "natural, vintage, traditional art", |
|
"params": { |
|
"guidance_scale": 8.0, |
|
"neon_intensity": 1.5, |
|
"volumetric_lighting": True, |
|
"resolution": (3840, 2160), |
|
} |
|
}, |
|
"Holographique": { |
|
"prompt": "holographic effect, iridescent colors, light refraction, transparent layers, futuristic", |
|
"negative_prompt": "flat, matte, solid colors", |
|
"params": { |
|
"iridescence": 1.0, |
|
"transparency": 0.7, |
|
"resolution": (2560, 1440), |
|
} |
|
}, |
|
|
|
} |
|
} |
|
|
|
class TextEffectProcessor: |
|
def __init__(self): |
|
self.effects = { |
|
"Réaliste": self._realistic_text, |
|
"Néon": self._neon_text, |
|
"Holographique": self._holographic_text, |
|
"3D": self._3d_text, |
|
"Vintage": self._vintage_text, |
|
"Graffiti": self._graffiti_text, |
|
"Matrix": self._matrix_text |
|
} |
|
|
|
def apply_effect(self, image: Image.Image, text: str, effect: str, position: Tuple[int, int]) -> Image.Image: |
|
if effect in self.effects: |
|
return self.effects[effect](image, text, position) |
|
return self._default_text(image, text, position) |
|
|
|
def _realistic_text(self, image: Image.Image, text: str, position: Tuple[int, int]) -> Image.Image: |
|
try: |
|
draw = ImageDraw.Draw(image) |
|
|
|
font = ImageFont.load_default() |
|
|
|
|
|
shadow_offset = 2 |
|
|
|
draw.text((position[0] + shadow_offset, position[1] + shadow_offset), |
|
text, font=font, fill=(0, 0, 0, 128)) |
|
|
|
draw.text(position, text, font=font, fill=(255, 255, 255)) |
|
|
|
return image |
|
except Exception as e: |
|
logger.error(f"Erreur lors du rendu réaliste: {str(e)}") |
|
return image |
|
|
|
def _neon_text(self, image: Image.Image, text: str, position: Tuple[int, int]) -> Image.Image: |
|
try: |
|
|
|
text_layer = Image.new('RGBA', image.size, (0, 0, 0, 0)) |
|
draw = ImageDraw.Draw(text_layer) |
|
font = ImageFont.load_default() |
|
|
|
|
|
glow_colors = [(255, 182, 193), (255, 192, 203), (255, 202, 213)] |
|
for i, color in enumerate(glow_colors): |
|
offset = (3 - i) * 2 |
|
draw.text((position[0] - offset, position[1] - offset), |
|
text, font=font, fill=color + (150,)) |
|
|
|
|
|
draw.text(position, text, font=font, fill=(255, 255, 255, 255)) |
|
|
|
|
|
return Image.alpha_composite(image.convert('RGBA'), text_layer) |
|
except Exception as e: |
|
logger.error(f"Erreur lors du rendu néon: {str(e)}") |
|
return image |
|
|
|
|
|
|
|
class ImageGenerator: |
|
def __init__(self): |
|
self.api_url = "https://api-inference.huggingface.co/models/stabilityai/stable-diffusion-xl-base-1.0" |
|
self.headers = {"Authorization": f"Bearer {os.getenv('HUGGINGFACE_TOKEN')}"} |
|
self.text_processor = TextEffectProcessor() |
|
|
|
async def generate_image(self, prompt: str, style_category: str, style_name: str, |
|
text: Optional[str] = None, text_effect: Optional[str] = None, |
|
position: Optional[Tuple[int, int]] = None) -> Tuple[Optional[Image.Image], str]: |
|
try: |
|
|
|
style_info = STYLE_CATEGORIES[style_category][style_name] |
|
|
|
|
|
final_prompt = f"{style_info['prompt']}, {prompt}" |
|
|
|
|
|
generation_params = { |
|
"inputs": final_prompt, |
|
"negative_prompt": style_info["negative_prompt"], |
|
"num_inference_steps": style_info["params"]["num_inference_steps"], |
|
"guidance_scale": style_info["params"]["guidance_scale"], |
|
} |
|
|
|
|
|
response = requests.post( |
|
self.api_url, |
|
headers=self.headers, |
|
json=generation_params, |
|
timeout=30 |
|
) |
|
|
|
if response.status_code != 200: |
|
return None, f"Erreur API: {response.status_code}" |
|
|
|
|
|
image = Image.open(io.BytesIO(response.content)) |
|
|
|
|
|
image = self._apply_style_effects(image, style_info["params"]) |
|
|
|
|
|
if text and text_effect: |
|
image = self.text_processor.apply_effect( |
|
image, text, text_effect, |
|
position or (image.width//2, image.height//2) |
|
) |
|
|
|
return image, "Génération réussie!" |
|
|
|
except Exception as e: |
|
logger.error(f"Erreur lors de la génération: {str(e)}") |
|
return None, f"Erreur: {str(e)}" |
|
|
|
def _apply_style_effects(self, image: Image.Image, style_params: Dict) -> Image.Image: |
|
"""Application des effets spécifiques au style""" |
|
try: |
|
|
|
img_array = np.array(image) |
|
|
|
|
|
if style_params.get("neon_intensity"): |
|
img_array = self._apply_neon_effect(img_array, style_params["neon_intensity"]) |
|
|
|
if style_params.get("volumetric_lighting"): |
|
img_array = self._apply_volumetric_lighting(img_array) |
|
|
|
|
|
return Image.fromarray(img_array) |
|
|
|
except Exception as e: |
|
logger.error(f"Erreur lors de l'application des effets: {str(e)}") |
|
return image |
|
|
|
def _apply_neon_effect(self, img_array: np.ndarray, intensity: float) -> np.ndarray: |
|
"""Applique un effet néon à l'image""" |
|
|
|
hsv = cv2.cvtColor(img_array, cv2.COLOR_RGB2HSV) |
|
|
|
hsv[..., 1] = np.clip(hsv[..., 1] * intensity, 0, 255) |
|
return cv2.cvtColor(hsv, cv2.COLOR_HSV2RGB) |
|
|
|
def _apply_volumetric_lighting(self, img_array: np.ndarray) -> np.ndarray: |
|
"""Ajoute un effet de lumière volumétrique""" |
|
|
|
gray = cv2.cvtColor(img_array, cv2.COLOR_RGB2GRAY) |
|
blur = cv2.GaussianBlur(gray, (0, 0), 15) |
|
return cv2.addWeighted(img_array, 1, cv2.cvtColor(blur, cv2.COLOR_GRAY2RGB), 0.2, 0) |
|
|
|
def create_interface(): |
|
generator = ImageGenerator() |
|
|
|
with gr.Blocks() as demo: |
|
gr.HTML("""<h1 style='text-align: center'>🎨 Equity Art Engine</h1>""") |
|
|
|
with gr.Row(): |
|
with gr.Column(scale=1): |
|
|
|
prompt = gr.Textbox(label="Description de l'image") |
|
style_category = gr.Dropdown( |
|
choices=list(STYLE_CATEGORIES.keys()), |
|
label="Catégorie de Style" |
|
) |
|
style_name = gr.Dropdown( |
|
label="Style Spécifique" |
|
) |
|
|
|
|
|
def update_styles(category): |
|
return gr.Dropdown.update( |
|
choices=list(STYLE_CATEGORIES[category].keys()) |
|
) |
|
style_category.change( |
|
update_styles, |
|
inputs=[style_category], |
|
outputs=[style_name] |
|
) |
|
|
|
|
|
text_input = gr.Textbox(label="Texte à ajouter (optionnel)") |
|
text_effect = gr.Dropdown( |
|
choices=["Réaliste", "Néon", "Holographique", "3D", "Vintage", "Graffiti", "Matrix"], |
|
label="Effet de texte" |
|
) |
|
|
|
with gr.Column(scale=2): |
|
|
|
image_output = gr.Image(label="Image générée") |
|
status_output = gr.Textbox(label="Status") |
|
|
|
|
|
generate_btn = gr.Button("Générer") |
|
|
|
|
|
def generate(prompt, category, style, text, effect): |
|
if not prompt or not category or not style: |
|
return None, "Veuillez remplir tous les champs requis" |
|
|
|
image, status = generator.generate_image( |
|
prompt=prompt, |
|
style_category=category, |
|
style_name=style, |
|
text=text if text else None, |
|
text_effect=effect if text else None |
|
) |
|
|
|
return image, status |
|
|
|
generate_btn.click( |
|
generate, |
|
inputs=[prompt, style_category, style_name, text_input, text_effect], |
|
outputs=[image_output, status_output] |
|
) |
|
|
|
return demo |
|
|
|
if __name__ == "__main__": |
|
demo = create_interface() |
|
demo.launch() |