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,
`;
} else if (prompt.includes(‘chien’) || prompt.includes(‘dog’)) {
// Générer un chien
generatedSvg = `data:image/svg+xml,
`;
} else if (prompt.includes(‘fleur’) || prompt.includes(‘flower’)) {
// Générer une fleur
generatedSvg = `data:image/svg+xml,
`;
} else if (prompt.includes(‘montagne’) || prompt.includes(‘mountain’)) {
// Générer une montagne
generatedSvg = `data:image/svg+xml,
`;
} 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,
`;
}
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’ && (
)}
{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 */}
{/* Couleur et style */}
updateSelectedElement({ color: e.target.value })}
className= »w-full h-10 border rounded-lg »
/>
{/* Position */}
{/* Rotation */}
updateSelectedElement({ rotation: parseInt(e.target.value) })}
className= »w-full »
/>
{/* Texte circulaire */}
{/* Options pour le texte circulaire */}
{selectedElement.isCircular && (
)}
{/* Ombre */}
{selectedElement.shadow && (
)}
)}
{!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 && (
<>
>
)}
{/* Vignette */}
)}
{activeTab === ‘ai’ && (
Génération d’image IA
setAiImageSettings({…aiImageSettings, negativePrompt: e.target.value})}
className= »w-full p-2 border rounded-lg »
placeholder= »Ce que vous ne voulez pas voir… »
/>
{generatedImageUrl && (
Image générée avec succès!
L’image a été intégrée à votre affiche.
)}
)}
{/* Aperçu du canvas */}
Aperçu
Dimensions: {posterSettings.width} × {posterSettings.height}px
Éléments de texte: {textElements.length}
{generatedImageUrl && (
✓ Image IA intégrée
)}
{selectedElement && (
✓ Élément sélectionné: {selectedElement.text}
)}
);
};
export default AdvancedPosterCreator;