Editeur d’images

import React, { useState, useRef, useEffect } from ‘react’; import { Download, Palette, Type, ImageIcon, Layers, Sparkles, Settings, Upload, Trash2, RotateCcw, Copy, Move, Plus, Edit3 } from ‘lucide-react’; const AdvancedPosterCreator = () => { const canvasRef = useRef(null); const [canvas, setCanvas] = useState(null); const [ctx, setCtx] = useState(null); // États pour les paramètres d’affiche const [posterSettings, setPosterSettings] = useState({ title: ‘Mon Affiche’, subtitle:  », description:  », width: 800, height: 1200, backgroundColor: ‘#ffffff’, backgroundImage: null, opacity: 1, gradientEnabled: false, gradientColor1: ‘#ffffff’, gradientColor2: ‘#000000’, gradientDirection: ‘vertical’ }); // États pour les éléments de texte const [textElements, setTextElements] = useState([ { id: 1, text: ‘Mon Affiche’, x: 400, y: 100, font: ‘Arial’, size: 48, color: ‘#000000’, weight: ‘bold’, align: ‘center’, shadow: false, shadowColor: ‘#888888’, shadowBlur: 4, shadowOffset: 2, rotation: 0, isCircular: false, circleRadius: 150, circleStartAngle: 0, selected: false } ]); // États pour l’édition de texte const [editingText, setEditingText] = useState(null); const [selectedElement, setSelectedElement] = useState(null); const [isDragging, setIsDragging] = useState(false); const [dragOffset, setDragOffset] = useState({ x: 0, y: 0 }); const [isTypographyMode, setIsTypographyMode] = useState(false); // États pour les effets visuels const [effects, setEffects] = useState({ borderEnabled: false, borderWidth: 5, borderColor: ‘#000000’, borderStyle: ‘solid’, shadowEnabled: false, shadowColor: ‘#000000’, shadowBlur: 10, shadowOffsetX: 5, shadowOffsetY: 5, overlayEnabled: false, overlayColor: ‘#000000’, overlayOpacity: 0.3, vignetteEnabled: false, vignetteIntensity: 0.5 }); // États pour la génération d’images IA const [aiImageSettings, setAiImageSettings] = useState({ prompt:  », negativePrompt: ‘blurry, low quality, distorted’, style: ‘realistic’, aspectRatio: ’16:9′, quality: ‘high’, seed: Math.floor(Math.random() * 1000000) }); const [isGeneratingImage, setIsGeneratingImage] = useState(false); const [generatedImageUrl, setGeneratedImageUrl] = useState( »); const [activeTab, setActiveTab] = useState(‘design’); // Fonts disponibles const availableFonts = [ ‘Arial’, ‘Helvetica’, ‘Times New Roman’, ‘Georgia’, ‘Verdana’, ‘Courier New’, ‘Impact’, ‘Comic Sans MS’, ‘Trebuchet MS’, ‘Palatino’, ‘Brush Script MT’, ‘Lucida Handwriting’, ‘Papyrus’, ‘Chiller’, ‘Old English Text MT’ ]; // Styles prédéfinis pour l’IA const aiStyles = [ { value: ‘realistic’, label: ‘Réaliste’ }, { value: ‘artistic’, label: ‘Artistique’ }, { value: ‘cartoon’, label: ‘Dessin animé’ }, { value: ‘abstract’, label: ‘Abstrait’ }, { value: ‘vintage’, label: ‘Vintage’ }, { value: ‘modern’, label: ‘Moderne’ }, { value: ‘cyberpunk’, label: ‘Cyberpunk’ }, { value: ‘fantasy’, label: ‘Fantasy’ } ]; // Ratios d’aspect const aspectRatios = [ { value: ‘1:1’, label: ‘Carré (1:1)’ }, { value: ‘4:3’, label: ‘Standard (4:3)’ }, { value: ’16:9′, label: ‘Paysage (16:9)’ }, { value: ‘9:16’, label: ‘Portrait (9:16)’ }, { value: ‘3:2’, label: ‘Photo (3:2)’ }, { value: ‘2:3’, label: ‘Portrait photo (2:3)’ } ]; useEffect(() => { const canvasElement = canvasRef.current; if (canvasElement) { const context = canvasElement.getContext(‘2d’); setCanvas(canvasElement); setCtx(context); } }, []); useEffect(() => { if (canvas && ctx) { drawPoster(); } }, [posterSettings, textElements, effects, generatedImageUrl, canvas, ctx]); const drawPoster = () => { if (!canvas || !ctx) return; canvas.width = posterSettings.width; canvas.height = posterSettings.height; // Effacer le canvas ctx.clearRect(0, 0, canvas.width, canvas.height); // Dessiner l’arrière-plan drawBackground(); // Ajouter les effets applyEffects(); // Dessiner l’image générée par IA si elle existe if (generatedImageUrl) { drawGeneratedImage(); } // Dessiner tous les éléments de texte textElements.forEach(element => { drawTextElement(element); }); // Appliquer la bordure if (effects.borderEnabled) { drawBorder(); } // Dessiner les contrôles de sélection if (selectedElement && isTypographyMode) { drawSelectionControls(selectedElement); } }; const drawBackground = () => { if (posterSettings.gradientEnabled) { const gradient = posterSettings.gradientDirection === ‘vertical’ ? ctx.createLinearGradient(0, 0, 0, canvas.height) : ctx.createLinearGradient(0, 0, canvas.width, 0); gradient.addColorStop(0, posterSettings.gradientColor1); gradient.addColorStop(1, posterSettings.gradientColor2); ctx.fillStyle = gradient; } else { ctx.fillStyle = posterSettings.backgroundColor; } ctx.globalAlpha = posterSettings.opacity; ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.globalAlpha = 1; }; const applyEffects = () => { if (effects.shadowEnabled) { ctx.shadowColor = effects.shadowColor; ctx.shadowBlur = effects.shadowBlur; ctx.shadowOffsetX = effects.shadowOffsetX; ctx.shadowOffsetY = effects.shadowOffsetY; } if (effects.overlayEnabled) { ctx.fillStyle = effects.overlayColor; ctx.globalAlpha = effects.overlayOpacity; ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.globalAlpha = 1; } if (effects.vignetteEnabled) { const gradient = ctx.createRadialGradient( canvas.width / 2, canvas.height / 2, 0, canvas.width / 2, canvas.height / 2, Math.max(canvas.width, canvas.height) / 2 ); gradient.addColorStop(0, ‘transparent’); gradient.addColorStop(1, `rgba(0,0,0,${effects.vignetteIntensity})`); ctx.fillStyle = gradient; ctx.fillRect(0, 0, canvas.width, canvas.height); } // Reset shadow ctx.shadowColor = ‘transparent’; ctx.shadowBlur = 0; ctx.shadowOffsetX = 0; ctx.shadowOffsetY = 0; }; const drawGeneratedImage = () => { const img = new Image(); img.onload = () => { const imgRatio = img.width / img.height; const canvasRatio = canvas.width / canvas.height; let drawWidth, drawHeight, drawX, drawY; if (imgRatio > canvasRatio) { drawWidth = canvas.width * 0.8; drawHeight = drawWidth / imgRatio; } else { drawHeight = canvas.height * 0.4; drawWidth = drawHeight * imgRatio; } drawX = (canvas.width – drawWidth) / 2; drawY = canvas.height * 0.35; ctx.drawImage(img, drawX, drawY, drawWidth, drawHeight); }; img.src = generatedImageUrl; }; const drawTextElement = (element) => { ctx.save(); // Configuration de base ctx.font = `${element.weight} ${element.size}px ${element.font}`; ctx.fillStyle = element.color; ctx.textAlign = element.align; // Ombre du texte if (element.shadow) { ctx.shadowColor = element.shadowColor; ctx.shadowBlur = element.shadowBlur; ctx.shadowOffsetX = element.shadowOffset; ctx.shadowOffsetY = element.shadowOffset; } if (element.isCircular) { drawCircularText(element); } else { // Rotation normale if (element.rotation !== 0) { ctx.translate(element.x, element.y); ctx.rotate((element.rotation * Math.PI) / 180); ctx.fillText(element.text, 0, 0); } else { ctx.fillText(element.text, element.x, element.y); } } ctx.restore(); }; const drawCircularText = (element) => { const text = element.text; const radius = element.circleRadius; const startAngle = (element.circleStartAngle * Math.PI) / 180; // Calculer l’angle par caractère const angleStep = (2 * Math.PI) / text.length; for (let i = 0; i < text.length; i++) { const char = text[i]; const angle = startAngle + (i * angleStep); const x = element.x + radius * Math.cos(angle); const y = element.y + radius * Math.sin(angle); ctx.save(); ctx.translate(x, y); ctx.rotate(angle + Math.PI / 2); ctx.fillText(char, 0, 0); ctx.restore(); } }; const drawSelectionControls = (element) => { if (!element.isCircular) { // Dessiner un rectangle de sélection pour le texte normal const metrics = ctx.measureText(element.text); const width = metrics.width; const height = element.size; ctx.strokeStyle = ‘#007bff’; ctx.lineWidth = 2; ctx.setLineDash([5, 5]); ctx.strokeRect(element.x – width/2, element.y – height, width, height); ctx.setLineDash([]); } else { // Dessiner le cercle de guidage pour le texte circulaire ctx.strokeStyle = ‘#007bff’; ctx.lineWidth = 2; ctx.setLineDash([5, 5]); ctx.beginPath(); ctx.arc(element.x, element.y, element.circleRadius, 0, 2 * Math.PI); ctx.stroke(); ctx.setLineDash([]); } }; const drawBorder = () => { ctx.strokeStyle = effects.borderColor; ctx.lineWidth = effects.borderWidth; ctx.setLineDash(effects.borderStyle === ‘dashed’ ? [10, 5] : []); ctx.strokeRect( effects.borderWidth / 2, effects.borderWidth / 2, canvas.width – effects.borderWidth, canvas.height – effects.borderWidth ); ctx.setLineDash([]); }; // Gestion des événements de canvas const getCanvasPosition = (e) => { const rect = canvas.getBoundingClientRect(); const x = e.clientX – rect.left; const y = e.clientY – rect.top; // Ajuster pour la taille réelle du canvas const scaleX = canvas.width / rect.width; const scaleY = canvas.height / rect.height; return { x: x * scaleX, y: y * scaleY }; }; const handleCanvasClick = (e) => { if (isDragging) return; // Ne pas traiter les clics pendant le drag const { x: canvasX, y: canvasY } = getCanvasPosition(e); // Vérifier si on clique sur un élément existant const clickedElement = textElements.find(element => { if (element.isCircular) { const distance = Math.sqrt(Math.pow(canvasX – element.x, 2) + Math.pow(canvasY – element.y, 2)); return Math.abs(distance – element.circleRadius) < 20; } else { ctx.font = `${element.weight} ${element.size}px ${element.font}`; const metrics = ctx.measureText(element.text); const width = metrics.width; const height = element.size; // Zone de sélection plus large pour faciliter la sélection return canvasX >= element.x – width/2 – 10 && canvasX <= element.x + width/2 + 10 && canvasY >= element.y – height – 10 && canvasY <= element.y + 10; } }); if (clickedElement) { setSelectedElement(clickedElement); } else if (isTypographyMode) { // Créer un nouveau élément de texte seulement en mode typographie const newElement = { id: Date.now(), text: 'Nouveau texte', x: canvasX, y: canvasY, font: 'Arial', size: 24, color: '#000000', weight: 'normal', align: 'center', shadow: false, shadowColor: '#888888', shadowBlur: 4, shadowOffset: 2, rotation: 0, isCircular: false, circleRadius: 100, circleStartAngle: 0, selected: false }; setTextElements([...textElements, newElement]); setSelectedElement(newElement); } }; const handleCanvasMouseDown = (e) => { if (!selectedElement) return; const { x: canvasX, y: canvasY } = getCanvasPosition(e); // Vérifier si on clique sur l’élément sélectionné pour commencer le drag let isOnElement = false; if (selectedElement.isCircular) { const distance = Math.sqrt(Math.pow(canvasX – selectedElement.x, 2) + Math.pow(canvasY – selectedElement.y, 2)); isOnElement = Math.abs(distance – selectedElement.circleRadius) < 30; } else { ctx.font = `${selectedElement.weight} ${selectedElement.size}px ${selectedElement.font}`; const metrics = ctx.measureText(selectedElement.text); const width = metrics.width; const height = selectedElement.size; isOnElement = canvasX >= selectedElement.x – width/2 – 10 && canvasX <= selectedElement.x + width/2 + 10 && canvasY >= selectedElement.y – height – 10 && canvasY <= selectedElement.y + 10; } if (isOnElement) { setIsDragging(true); setDragOffset({ x: canvasX - selectedElement.x, y: canvasY - selectedElement.y }); canvas.style.cursor = 'grabbing'; } }; const handleCanvasMouseMove = (e) => { const { x: canvasX, y: canvasY } = getCanvasPosition(e); if (isDragging && selectedElement) { const newX = canvasX – dragOffset.x; const newY = canvasY – dragOffset.y; // Contraindre le déplacement dans les limites du canvas const constrainedX = Math.max(0, Math.min(canvas.width, newX)); const constrainedY = Math.max(0, Math.min(canvas.height, newY)); setTextElements(prev => prev.map(element => element.id === selectedElement.id ? { …element, x: constrainedX, y: constrainedY } : element )); setSelectedElement({ …selectedElement, x: constrainedX, y: constrainedY }); } else if (selectedElement) { // Changer le curseur quand on survole l’élément sélectionné let isOnElement = false; if (selectedElement.isCircular) { const distance = Math.sqrt(Math.pow(canvasX – selectedElement.x, 2) + Math.pow(canvasY – selectedElement.y, 2)); isOnElement = Math.abs(distance – selectedElement.circleRadius) < 30; } else { ctx.font = `${selectedElement.weight} ${selectedElement.size}px ${selectedElement.font}`; const metrics = ctx.measureText(selectedElement.text); const width = metrics.width; const height = selectedElement.size; isOnElement = canvasX >= selectedElement.x – width/2 – 10 && canvasX <= selectedElement.x + width/2 + 10 && canvasY >= selectedElement.y – height – 10 && canvasY <= selectedElement.y + 10; } canvas.style.cursor = isOnElement ? 'grab' : (isTypographyMode ? 'crosshair' : 'default'); } else { canvas.style.cursor = isTypographyMode ? 'crosshair' : 'default'; } }; const handleCanvasMouseUp = () => { if (isDragging) { setIsDragging(false); canvas.style.cursor = selectedElement ? ‘grab’ : (isTypographyMode ? ‘crosshair’ : ‘default’); } }; const addNewTextElement = () => { const newElement = { id: Date.now(), text: ‘Nouveau texte’, x: canvas.width / 2, y: canvas.height / 2, font: ‘Arial’, size: 24, color: ‘#000000’, weight: ‘normal’, align: ‘center’, shadow: false, shadowColor: ‘#888888’, shadowBlur: 4, shadowOffset: 2, rotation: 0, isCircular: false, circleRadius: 100, circleStartAngle: 0, selected: false }; setTextElements([…textElements, newElement]); setSelectedElement(newElement); }; const updateSelectedElement = (updates) => { if (!selectedElement) return; const updatedElement = { …selectedElement, …updates }; setTextElements(prev => prev.map(element => element.id === selectedElement.id ? updatedElement : element )); setSelectedElement(updatedElement); }; const deleteSelectedElement = () => { if (!selectedElement) return; setTextElements(prev => prev.filter(element => element.id !== selectedElement.id)); setSelectedElement(null); }; const generateAIImage = async () => { setIsGeneratingImage(true); try { // Construction d’un prompt plus précis et détaillé let enrichedPrompt = aiImageSettings.prompt; // Enrichir le prompt selon le style choisi switch (aiImageSettings.style) { case ‘realistic’: enrichedPrompt += ‘, photorealistic, high quality, detailed, professional photography’; break; case ‘artistic’: enrichedPrompt += ‘, artistic painting, brush strokes, creative art style’; break; case ‘cartoon’: enrichedPrompt += ‘, cartoon illustration, animated style, colorful, fun’; break; case ‘abstract’: enrichedPrompt += ‘, abstract art, geometric shapes, modern art’; break; case ‘vintage’: enrichedPrompt += ‘, vintage style, retro, aged paper, classic’; break; case ‘modern’: enrichedPrompt += ‘, modern design, contemporary, clean, minimalist’; break; case ‘cyberpunk’: enrichedPrompt += ‘, cyberpunk style, neon lights, futuristic, dark atmosphere’; break; case ‘fantasy’: enrichedPrompt += ‘, fantasy art, magical, ethereal, mystical atmosphere’; break; } console.log(‘Génération d\’image avec prompt enrichi:’, enrichedPrompt); // Simulation d’une génération d’image plus réaliste basée sur le prompt await new Promise(resolve => setTimeout(resolve, 2500)); // Générer une image SVG qui reflète mieux le prompt demandé let generatedSvg =  »; const prompt = aiImageSettings.prompt.toLowerCase(); if (prompt.includes(‘chat’) || prompt.includes(‘cat’)) { // Générer un chat stylisé const catColor = prompt.includes(‘bleu’) ? ‘#4A90E2’ : prompt.includes(‘rouge’) ? ‘#E74C3C’ : prompt.includes(‘vert’) ? ‘#2ECC71’ : ‘#8B4513’; generatedSvg = `data:image/svg+xml, Chat ${prompt.includes(‘bleu’) ? ‘bleu’ :  »} généré par IA `; } else if (prompt.includes(‘chien’) || prompt.includes(‘dog’)) { // Générer un chien generatedSvg = `data:image/svg+xml, Chien généré par IA `; } else if (prompt.includes(‘fleur’) || prompt.includes(‘flower’)) { // Générer une fleur generatedSvg = `data:image/svg+xml, Fleur générée par IA `; } else if (prompt.includes(‘montagne’) || prompt.includes(‘mountain’)) { // Générer une montagne generatedSvg = `data:image/svg+xml, Montagne générée par IA `; } else { // Image générique basée sur le style const baseColor = aiImageSettings.style === ‘cyberpunk’ ? ‘#00ffff’ : aiImageSettings.style === ‘vintage’ ? ‘#8b4513’ : aiImageSettings.style === ‘fantasy’ ? ‘#9932cc’ : ‘#4ecdc4’; generatedSvg = `data:image/svg+xml, ${aiImageSettings.style.toUpperCase()} « ${aiImageSettings.prompt.substring(0, 30)}… » – IA `; } setGeneratedImageUrl(generatedSvg); } catch (error) { console.error(‘Erreur lors de la génération:’, error); alert(‘Erreur lors de la génération de l\’image. Veuillez réessayer.’); } finally { setIsGeneratingImage(false); } }; const downloadPoster = () => { const link = document.createElement(‘a’); link.download = `${posterSettings.title || ‘affiche’}.png`; link.href = canvas.toDataURL(); link.click(); }; const resetToDefaults = () => { setPosterSettings({ title: ‘Mon Affiche’, subtitle:  », description:  », width: 800, height: 1200, backgroundColor: ‘#ffffff’, backgroundImage: null, opacity: 1, gradientEnabled: false, gradientColor1: ‘#ffffff’, gradientColor2: ‘#000000’, gradientDirection: ‘vertical’ }); setTextElements([ { id: 1, text: ‘Mon Affiche’, x: 400, y: 100, font: ‘Arial’, size: 48, color: ‘#000000’, weight: ‘bold’, align: ‘center’, shadow: false, shadowColor: ‘#888888’, shadowBlur: 4, shadowOffset: 2, rotation: 0, isCircular: false, circleRadius: 150, circleStartAngle: 0, selected: false } ]); setEffects({ borderEnabled: false, borderWidth: 5, borderColor: ‘#000000’, borderStyle: ‘solid’, shadowEnabled: false, shadowColor: ‘#000000’, shadowBlur: 10, shadowOffsetX: 5, shadowOffsetY: 5, overlayEnabled: false, overlayColor: ‘#000000’, overlayOpacity: 0.3, vignetteEnabled: false, vignetteIntensity: 0.5 }); setGeneratedImageUrl( »); setSelectedElement(null); }; const TabButton = ({ id, label, icon: Icon }) => ( ); return (

Créateur d’Affiche Avancé

Créez des affiches professionnelles avec l’IA et la typographie avancée

{/* Panel de contrôle */}
{/* Onglets */}
{/* Mode typographie indicator */} {isTypographyMode && (
Mode Typographie activé

Cliquez sur le canvas pour ajouter du texte ou sélectionner un élément existant

)} {/* Actions rapides */}
{activeTab === ‘typography’ && ( )}
{/* Contenu des onglets */} {activeTab === ‘design’ && (

Paramètres généraux

setPosterSettings({…posterSettings, width: parseInt(e.target.value)})} className= »w-full p-2 border rounded-lg » min= »200″ max= »2000″ />
setPosterSettings({…posterSettings, height: parseInt(e.target.value)})} className= »w-full p-2 border rounded-lg » min= »200″ max= »2000″ />
setPosterSettings({…posterSettings, title: e.target.value})} className= »w-full p-2 border rounded-lg » placeholder= »Titre de votre affiche » />

Arrière-plan

{posterSettings.gradientEnabled ? ( <>
setPosterSettings({…posterSettings, gradientColor1: e.target.value})} className= »w-full h-10 border rounded-lg » />
setPosterSettings({…posterSettings, gradientColor2: e.target.value})} className= »w-full h-10 border rounded-lg » />
) : (
setPosterSettings({…posterSettings, backgroundColor: e.target.value})} className= »w-full h-10 border rounded-lg » />
)}
setPosterSettings({…posterSettings, opacity: parseFloat(e.target.value)})} className= »w-full » />
)} {activeTab === ‘typography’ && (
{/* Liste des éléments de texte */}

Éléments de texte

{textElements.map((element, index) => (
setSelectedElement(element)} className={`p-3 border rounded-lg cursor-pointer transition-colors ${ selectedElement?.id === element.id ? ‘border-blue-500 bg-blue-50’ : ‘border-gray-200 hover:border-gray-300’ }`} >

{element.text || ‘Texte vide’}

{element.font} • {element.size}px • {element.isCircular ? ‘Circulaire’ : ‘Normal’}

))}
{/* Propriétés de l’élément sélectionné */} {selectedElement && (

Propriétés du texte

{/* Texte */}
updateSelectedElement({ text: e.target.value })} className= »w-full p-2 border rounded-lg » placeholder= »Votre texte… » />
{/* Police et taille */}
updateSelectedElement({ size: parseInt(e.target.value) })} className= »w-full p-2 border rounded-lg » min= »8″ max= »200″ />
{/* Couleur et style */}
updateSelectedElement({ color: e.target.value })} className= »w-full h-10 border rounded-lg » />
{/* Position */}
updateSelectedElement({ x: parseInt(e.target.value) })} className= »w-full p-2 border rounded-lg » />
updateSelectedElement({ y: parseInt(e.target.value) })} className= »w-full p-2 border rounded-lg » />
{/* Rotation */}
updateSelectedElement({ rotation: parseInt(e.target.value) })} className= »w-full » />
{/* Texte circulaire */}
{/* Options pour le texte circulaire */} {selectedElement.isCircular && (
updateSelectedElement({ circleRadius: parseInt(e.target.value) })} className= »w-full » />
updateSelectedElement({ circleStartAngle: parseInt(e.target.value) })} className= »w-full » />
)} {/* Ombre */}
{selectedElement.shadow && (
updateSelectedElement({ shadowColor: e.target.value })} className= »w-full h-8 border rounded-lg » />
updateSelectedElement({ shadowBlur: parseInt(e.target.value) })} className= »w-full » />
)}
)} {!selectedElement && (

Aucun élément sélectionné

Cliquez sur le canvas pour ajouter du texte ou sélectionnez un élément dans la liste ci-dessus

)}
)} {activeTab === ‘effects’ && (
{/* Bordure */}

Bordure

{effects.borderEnabled && ( <>
setEffects({…effects, borderWidth: parseInt(e.target.value)})} className= »w-full p-2 border rounded-lg » min= »1″ max= »50″ />
setEffects({…effects, borderColor: e.target.value})} className= »w-full h-10 border rounded-lg » />
)}
{/* Vignette */}

Vignette

{effects.vignetteEnabled && (
setEffects({…effects, vignetteIntensity: parseFloat(e.target.value)})} className= »w-full » />
)}
)} {activeTab === ‘ai’ && (

Génération d’image IA