|
function rgbToHex(r, g, b) { |
|
return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1); |
|
} |
|
|
|
function hexToRgb(hex) { |
|
hex = hex.replace(/^#/, ""); |
|
if (hex.length === 3) |
|
hex = hex |
|
.split("") |
|
.map((c) => c + c) |
|
.join(""); |
|
const num = parseInt(hex, 16); |
|
return { |
|
r: (num >> 16) & 255, |
|
g: (num >> 8) & 255, |
|
b: num & 255 |
|
}; |
|
} |
|
|
|
function rgbToHsv(r, g, b) { |
|
r /= 255; |
|
g /= 255; |
|
b /= 255; |
|
const max = Math.max(r, g, b), |
|
min = Math.min(r, g, b); |
|
let h, |
|
s, |
|
v = max; |
|
const d = max - min; |
|
s = max === 0 ? 0 : d / max; |
|
if (max === min) h = 0; |
|
else { |
|
switch (max) { |
|
case r: |
|
h = (g - b) / d + (g < b ? 6 : 0); |
|
break; |
|
case g: |
|
h = (b - r) / d + 2; |
|
break; |
|
case b: |
|
h = (r - g) / d + 4; |
|
break; |
|
} |
|
h /= 6; |
|
} |
|
return { |
|
h: Math.round(h * 360), |
|
s: Math.round(s * 100), |
|
v: Math.round(v * 100) |
|
}; |
|
} |
|
|
|
function hsvToRgb(h, s, v) { |
|
h /= 360; |
|
s /= 100; |
|
v /= 100; |
|
let r, g, b; |
|
const i = Math.floor(h * 6); |
|
const f = h * 6 - i; |
|
const p = v * (1 - s); |
|
const q = v * (1 - f * s); |
|
const t = v * (1 - (1 - f) * s); |
|
switch (i % 6) { |
|
case 0: |
|
r = v; |
|
g = t; |
|
b = p; |
|
break; |
|
case 1: |
|
r = q; |
|
g = v; |
|
b = p; |
|
break; |
|
case 2: |
|
r = p; |
|
g = v; |
|
b = t; |
|
break; |
|
case 3: |
|
r = p; |
|
g = q; |
|
b = v; |
|
break; |
|
case 4: |
|
r = t; |
|
g = p; |
|
b = v; |
|
break; |
|
case 5: |
|
r = v; |
|
g = p; |
|
b = q; |
|
break; |
|
} |
|
return { |
|
r: Math.round(r * 255), |
|
g: Math.round(g * 255), |
|
b: Math.round(b * 255) |
|
}; |
|
} |
|
|
|
function loadImageWithCORS(url) { |
|
return new Promise((resolve, reject) => { |
|
const img = new Image(); |
|
img.crossOrigin = "anonymous"; |
|
img.onload = () => resolve(img); |
|
img.onerror = () => { |
|
const img2 = new Image(); |
|
img2.onload = () => resolve(img2); |
|
img2.onerror = reject; |
|
img2.src = url; |
|
}; |
|
img.src = url; |
|
console.log("&Toc on codepen - https://codepen.io/ol-ivier"); |
|
}); |
|
} |
|
const image = document.getElementById("meme-image"); |
|
const imageUpload = document.getElementById("image-upload"); |
|
const customUploadBtn = document.getElementById("custom-upload-btn"); |
|
const fileName = document.getElementById("file-name"); |
|
const effectsList = document.getElementById("effects-list"); |
|
const effectsPanel = document.getElementById("effectsPanel"); |
|
const toggleBtn = document.getElementById("togglePanelBtn"); |
|
const exportBtn = document.getElementById("export-btn"); |
|
console.log("&Toc on codepen - https://codepen.io/ol-ivier"); |
|
customUploadBtn.addEventListener("click", () => { |
|
imageUpload.click(); |
|
}); |
|
imageUpload.addEventListener("change", function (e) { |
|
if (e.target.files && e.target.files[0]) { |
|
const file = e.target.files[0]; |
|
fileName.textContent = `${file.name}`; |
|
if (file.type.match("image.*")) { |
|
const reader = new FileReader(); |
|
reader.onload = (event) => { |
|
image.src = event.target.result; |
|
image.crossOrigin = "anonymous"; |
|
}; |
|
reader.readAsDataURL(file); |
|
document.querySelector("#infos").style.display = "none"; |
|
} |
|
} |
|
}); |
|
toggleBtn.addEventListener("click", () => { |
|
effectsPanel.classList.toggle("hidden"); |
|
toggleBtn.classList.toggle("rotated"); |
|
}); |
|
const effects = [ |
|
{ |
|
name: "Flou", |
|
property: "blur", |
|
unit: "px", |
|
min: 0, |
|
max: 10, |
|
default: 0, |
|
step: 0.1, |
|
type: "filter" |
|
}, |
|
{ |
|
name: "Luminosité", |
|
property: "brightness", |
|
unit: "%", |
|
min: 20, |
|
max: 200, |
|
default: 100, |
|
step: 1, |
|
type: "filter" |
|
}, |
|
{ |
|
name: "Contraste", |
|
property: "contrast", |
|
unit: "%", |
|
min: 12, |
|
max: 300, |
|
default: 100, |
|
step: 1, |
|
type: "filter" |
|
}, |
|
{ |
|
name: "Saturation", |
|
property: "saturate", |
|
unit: "%", |
|
min: 0, |
|
max: 600, |
|
default: 100, |
|
step: 1, |
|
type: "filter" |
|
}, |
|
{ |
|
name: "Teinte", |
|
property: "hue-rotate", |
|
unit: "deg", |
|
min: 0, |
|
max: 360, |
|
default: 0, |
|
step: 1, |
|
type: "filter" |
|
}, |
|
{ |
|
name: "Sépia", |
|
property: "sepia", |
|
unit: "%", |
|
min: 0, |
|
max: 100, |
|
default: 0, |
|
step: 1, |
|
type: "filter" |
|
}, |
|
{ |
|
name: "Négatif", |
|
property: "invert", |
|
unit: "%", |
|
min: 0, |
|
max: 100, |
|
default: 0, |
|
step: 1, |
|
type: "filter" |
|
}, |
|
{ |
|
name: "Niveaux gris", |
|
property: "grayscale", |
|
unit: "%", |
|
min: 0, |
|
max: 100, |
|
default: 0, |
|
step: 1, |
|
type: "filter" |
|
}, |
|
{ |
|
name: "Opacité", |
|
property: "opacity", |
|
unit: "%", |
|
min: 10, |
|
max: 100, |
|
default: 100, |
|
step: 1, |
|
type: "filter" |
|
}, |
|
{ |
|
name: "Zoom*", |
|
property: "scale", |
|
unit: "%", |
|
min: 50, |
|
max: 200, |
|
default: 100, |
|
step: 1, |
|
type: "transform" |
|
}, |
|
{ |
|
name: "Rotation", |
|
property: "rotate", |
|
unit: "deg", |
|
min: 0, |
|
max: 360, |
|
default: 0, |
|
step: 1, |
|
type: "transform" |
|
}, |
|
{ |
|
name: "Inclinaison X", |
|
property: "skewX", |
|
unit: "deg", |
|
min: -45, |
|
max: 45, |
|
default: 0, |
|
step: 1, |
|
type: "transform" |
|
}, |
|
{ |
|
name: "Inclinaison Y", |
|
property: "skewY", |
|
unit: "deg", |
|
min: -45, |
|
max: 45, |
|
default: 0, |
|
step: 1, |
|
type: "transform" |
|
}, |
|
{ |
|
name: "Ombre X", |
|
property: "shadowX", |
|
unit: "px", |
|
min: -20, |
|
max: 20, |
|
default: 0, |
|
step: 1, |
|
type: "shadow" |
|
}, |
|
{ |
|
name: "Ombre Y", |
|
property: "shadowY", |
|
unit: "px", |
|
min: -20, |
|
max: 20, |
|
default: 0, |
|
step: 1, |
|
type: "shadow" |
|
}, |
|
{ |
|
name: "Flou ombre", |
|
property: "shadowBlur", |
|
unit: "px", |
|
min: 0, |
|
max: 200, |
|
default: 0, |
|
step: 1, |
|
type: "shadow" |
|
}, |
|
{ |
|
name: "Bords arrondis", |
|
property: "borderRadius", |
|
unit: "px", |
|
min: 0, |
|
max: 100, |
|
default: 0, |
|
step: 1, |
|
type: "border" |
|
}, |
|
{ |
|
name: "Néon", |
|
property: "neon", |
|
unit: "", |
|
min: 0, |
|
max: 100, |
|
default: 0, |
|
step: 1, |
|
type: "neon" |
|
}, |
|
{ |
|
name: "Arc-en-ciel*", |
|
property: "rainbow", |
|
unit: "", |
|
min: 0, |
|
max: 100, |
|
default: 0, |
|
step: 1, |
|
type: "rainbow" |
|
}, |
|
{ |
|
name: "Masque vintage", |
|
property: "vintage", |
|
unit: "", |
|
min: 0, |
|
max: 100, |
|
default: 0, |
|
step: 1, |
|
type: "vintage" |
|
}, |
|
{ |
|
name: "Hologramme*", |
|
property: "hologram", |
|
unit: "", |
|
min: 0, |
|
max: 100, |
|
default: 0, |
|
step: 1, |
|
type: "hologram" |
|
}, |
|
{ |
|
name: "Coucher soleil", |
|
property: "sunset", |
|
unit: "", |
|
min: 0, |
|
max: 100, |
|
default: 0, |
|
step: 1, |
|
type: "sunset" |
|
}, |
|
{ |
|
name: "Givre", |
|
property: "frost", |
|
unit: "", |
|
min: 0, |
|
max: 100, |
|
default: 0, |
|
step: 1, |
|
type: "frost" |
|
}, |
|
{ |
|
name: "Feu d'artifice", |
|
property: "fireworks", |
|
unit: "", |
|
min: 0, |
|
max: 100, |
|
default: 0, |
|
step: 1, |
|
type: "fireworks" |
|
} |
|
]; |
|
let enabledEffects = {}; |
|
let shadowValues = { |
|
x: 0, |
|
y: 0, |
|
blur: 0 |
|
}; |
|
effects.forEach((effect) => { |
|
enabledEffects[effect.property] = !0; |
|
}); |
|
effects.forEach((effect) => { |
|
const effectDiv = document.createElement("div"); |
|
effectDiv.className = "effect-item"; |
|
effectDiv.innerHTML = ` |
|
<div class="effect-header"> |
|
<input type="checkbox" id="check-${effect.property}" checked> |
|
<label for="check-${effect.property}">${effect.name}</label> |
|
<span class="value-display" id="display-${effect.property}">${effect.default}${effect.unit}</span> |
|
</div> |
|
<div class="slider-container"> |
|
<input type="range" id="${effect.property}" |
|
min="${effect.min}" max="${effect.max}" |
|
value="${effect.default}" step="${effect.step}"> |
|
</div> |
|
`; |
|
effectsList.appendChild(effectDiv); |
|
}); |
|
|
|
function updateDisplay(effect) { |
|
const slider = document.getElementById(effect.property); |
|
const display = document.getElementById(`display-${effect.property}`); |
|
if (slider && display) { |
|
const value = parseFloat(slider.value); |
|
display.textContent = value + effect.unit; |
|
} |
|
} |
|
|
|
function applyEffects() { |
|
effects.forEach((effect) => updateDisplay(effect)); |
|
const shadowX = enabledEffects.shadowX |
|
? parseFloat(document.getElementById("shadowX")?.value || 0) |
|
: 0; |
|
const shadowY = enabledEffects.shadowY |
|
? parseFloat(document.getElementById("shadowY")?.value || 0) |
|
: 0; |
|
const shadowBlur = enabledEffects.shadowBlur |
|
? parseFloat(document.getElementById("shadowBlur")?.value || 0) |
|
: 0; |
|
shadowValues = { |
|
x: shadowX, |
|
y: shadowY, |
|
blur: shadowBlur |
|
}; |
|
let filterString = ""; |
|
let transformString = ""; |
|
let borderRadiusString = ""; |
|
effects.forEach((effect) => { |
|
const slider = document.getElementById(effect.property); |
|
if (slider && enabledEffects[effect.property]) { |
|
const value = parseFloat(slider.value); |
|
switch (effect.type) { |
|
case "filter": |
|
if (effect.property === "opacity") { |
|
filterString += ` opacity(${value}%)`; |
|
} else if (effect.property === "hue-rotate") { |
|
filterString += ` hue-rotate(${value}deg)`; |
|
} else if (value !== effect.default) { |
|
filterString += ` ${effect.property}(${value}${effect.unit})`; |
|
} |
|
break; |
|
case "transform": |
|
if (effect.property === "scale") { |
|
transformString += ` scale(${value / 100})`; |
|
} else if (value !== effect.default) { |
|
transformString += ` ${effect.property}(${value}${effect.unit})`; |
|
} |
|
break; |
|
case "border": |
|
if (value !== effect.default) { |
|
borderRadiusString = `${value}px`; |
|
} |
|
break; |
|
case "neon": |
|
if (value > 0) { |
|
const neonIntensity = 1 + value / 50; |
|
filterString += ` brightness(${neonIntensity}) saturate(${neonIntensity})`; |
|
} |
|
break; |
|
case "rainbow": |
|
if (value > 0) { |
|
const hue = ((Date.now() * value) / 1000) % 360; |
|
filterString += ` hue-rotate(${hue}deg) saturate(2)`; |
|
} |
|
break; |
|
case "vintage": |
|
if (value > 0) { |
|
const v = value / 100; |
|
filterString += ` sepia(${0.3 + v * 0.7}) contrast(${ |
|
1 + v * 0.5 |
|
}) brightness(${0.9 + v * 0.2})`; |
|
if (value > 50) filterString += ` blur(0.5px)`; |
|
} |
|
break; |
|
case "hologram": |
|
if (value > 0) { |
|
const h = value / 100; |
|
const hueShift = (Date.now() * 0.1) % 360; |
|
filterString += ` hue-rotate(${hueShift}deg) saturate(${ |
|
1 + h * 2 |
|
}) brightness(${1 + h * 0.5})`; |
|
} |
|
break; |
|
case "sunset": |
|
if (value > 0) { |
|
const s = value / 100; |
|
filterString += ` sepia(${0.3 + s * 0.5}) hue-rotate(${ |
|
-10 + s * 20 |
|
}deg) saturate(${1.2 + s}) brightness(${1 + s * 0.3})`; |
|
} |
|
break; |
|
case "frost": |
|
if (value > 0) { |
|
const f = value / 100; |
|
filterString += ` grayscale(${ |
|
0.2 + f * 0.5 |
|
}) brightness(${1.2}) contrast(${1 - f * 0.3}) blur(${ |
|
f * 2 |
|
}px) sepia(0.2) hue-rotate(180deg)`; |
|
} |
|
break; |
|
case "fireworks": |
|
if (value > 0) { |
|
filterString += ` brightness(${1.5}) saturate(${3}) contrast(1.2)`; |
|
} |
|
break; |
|
} |
|
} |
|
}); |
|
const hologramActive = |
|
enabledEffects.hologram && |
|
parseFloat(document.getElementById("hologram")?.value || 0) > 0; |
|
const fireworksActive = |
|
enabledEffects.fireworks && |
|
parseFloat(document.getElementById("fireworks")?.value || 0) > 0; |
|
if (!hologramActive && !fireworksActive) { |
|
if (shadowValues.x !== 0 || shadowValues.y !== 0 || shadowValues.blur !== 0) { |
|
image.style.boxShadow = `${shadowValues.x}px ${shadowValues.y}px ${shadowValues.blur}px rgba(0,0,0,0.5)`; |
|
} else { |
|
image.style.boxShadow = "none"; |
|
} |
|
} else { |
|
image.style.boxShadow = "none"; |
|
} |
|
image.style.filter = filterString || "none"; |
|
image.style.transform = transformString || "none"; |
|
image.style.borderRadius = borderRadiusString || "0"; |
|
} |
|
effects.forEach((effect) => { |
|
const slider = document.getElementById(effect.property); |
|
if (slider) { |
|
slider.addEventListener("input", () => { |
|
updateDisplay(effect); |
|
applyEffects(); |
|
}); |
|
} |
|
const checkbox = document.getElementById(`check-${effect.property}`); |
|
if (checkbox) { |
|
checkbox.addEventListener("change", function () { |
|
enabledEffects[effect.property] = this.checked; |
|
const slider = document.getElementById(effect.property); |
|
if (slider) slider.disabled = !this.checked; |
|
applyEffects(); |
|
}); |
|
} |
|
}); |
|
document.getElementById("reset-all").addEventListener("click", function () { |
|
effects.forEach((effect) => { |
|
const slider = document.getElementById(effect.property); |
|
const checkbox = document.getElementById(`check-${effect.property}`); |
|
if (slider && checkbox) { |
|
slider.value = effect.default; |
|
checkbox.checked = !0; |
|
enabledEffects[effect.property] = !0; |
|
slider.disabled = !1; |
|
updateDisplay(effect); |
|
} |
|
}); |
|
image.style.boxShadow = "none"; |
|
applyEffects(); |
|
fileName.textContent = ""; |
|
}); |
|
effects.forEach((effect) => updateDisplay(effect)); |
|
applyEffects(); |
|
|
|
function animateEffects() { |
|
if ( |
|
enabledEffects.rainbow && |
|
parseFloat(document.getElementById("rainbow")?.value || 0) > 0 |
|
) { |
|
applyEffects(); |
|
} |
|
if ( |
|
enabledEffects.hologram && |
|
parseFloat(document.getElementById("hologram")?.value || 0) > 0 |
|
) { |
|
applyEffects(); |
|
} |
|
if ( |
|
enabledEffects.fireworks && |
|
parseFloat(document.getElementById("fireworks")?.value || 0) > 0 |
|
) { |
|
applyEffects(); |
|
} |
|
requestAnimationFrame(animateEffects); |
|
} |
|
animateEffects(); |
|
|
|
function createLoadingOverlay() { |
|
const overlay = document.createElement("div"); |
|
overlay.id = "export-loading-overlay"; |
|
const spinner = document.createElement("div"); |
|
spinner.className = "export-spinner"; |
|
const message = document.createElement("div"); |
|
message.textContent = "Export en cours..."; |
|
message.style.fontSize = "18px"; |
|
message.style.fontWeight = "500"; |
|
message.style.marginBottom = "10px"; |
|
const subMessage = document.createElement("div"); |
|
subMessage.id = "export-status"; |
|
subMessage.textContent = "Préparation de l'image"; |
|
subMessage.style.fontSize = "14px"; |
|
subMessage.style.opacity = "0.8"; |
|
overlay.appendChild(spinner); |
|
overlay.appendChild(message); |
|
overlay.appendChild(subMessage); |
|
return overlay; |
|
} |
|
if (!CanvasRenderingContext2D.prototype.roundRect) { |
|
CanvasRenderingContext2D.prototype.roundRect = function (x, y, w, h, r) { |
|
if (w < 2 * r) r = w / 2; |
|
if (h < 2 * r) r = h / 2; |
|
this.moveTo(x + r, y); |
|
this.lineTo(x + w - r, y); |
|
this.quadraticCurveTo(x + w, y, x + w, y + r); |
|
this.lineTo(x + w, y + h - r); |
|
this.quadraticCurveTo(x + w, y + h, x + w - r, y + h); |
|
this.lineTo(x + r, y + h); |
|
this.quadraticCurveTo(x, y + h, x, y + h - r); |
|
this.lineTo(x, y + r); |
|
this.quadraticCurveTo(x, y, x + r, y); |
|
return this; |
|
}; |
|
} |
|
async function exportImage() { |
|
exportBtn.disabled = !0; |
|
exportBtn.style.opacity = "0.7"; |
|
exportBtn.innerHTML = "Export..."; |
|
const loadingOverlay = createLoadingOverlay(); |
|
document.body.appendChild(loadingOverlay); |
|
try { |
|
if (!image.complete || image.naturalWidth === 0) { |
|
throw new Error("L'image n'est pas complètement chargée"); |
|
} |
|
const statusEl = document.getElementById("export-status"); |
|
const imgWidth = image.naturalWidth; |
|
const imgHeight = image.naturalHeight; |
|
const scale = enabledEffects.scale |
|
? parseFloat(document.getElementById("scale")?.value || 100) / 100 |
|
: 1; |
|
const rotate = enabledEffects.rotate |
|
? parseFloat(document.getElementById("rotate")?.value || 0) |
|
: 0; |
|
const skewX = enabledEffects.skewX |
|
? parseFloat(document.getElementById("skewX")?.value || 0) |
|
: 0; |
|
const skewY = enabledEffects.skewY |
|
? parseFloat(document.getElementById("skewY")?.value || 0) |
|
: 0; |
|
const shadowX = enabledEffects.shadowX |
|
? parseFloat(document.getElementById("shadowX")?.value || 0) |
|
: 0; |
|
const shadowY = enabledEffects.shadowY |
|
? parseFloat(document.getElementById("shadowY")?.value || 0) |
|
: 0; |
|
const shadowBlur = enabledEffects.shadowBlur |
|
? parseFloat(document.getElementById("shadowBlur")?.value || 0) |
|
: 0; |
|
const borderRadius = enabledEffects.borderRadius |
|
? parseFloat(document.getElementById("borderRadius")?.value || 0) |
|
: 0; |
|
const angleRad = (rotate * Math.PI) / 180; |
|
const skewXRad = (skewX * Math.PI) / 180; |
|
const skewYRad = (skewY * Math.PI) / 180; |
|
const scaledWidth = imgWidth * scale; |
|
const scaledHeight = imgHeight * scale; |
|
const points = [ |
|
{ |
|
x: 0, |
|
y: 0 |
|
}, |
|
{ |
|
x: scaledWidth, |
|
y: 0 |
|
}, |
|
{ |
|
x: scaledWidth, |
|
y: scaledHeight |
|
}, |
|
{ |
|
x: 0, |
|
y: scaledHeight |
|
} |
|
].map((p) => ({ |
|
x: p.x + p.y * Math.tan(skewXRad), |
|
y: p.y + p.x * Math.tan(skewYRad) |
|
})); |
|
const centerX = (points[0].x + points[2].x) / 2; |
|
const centerY = (points[0].y + points[2].y) / 2; |
|
const rotatedPoints = points.map((p) => { |
|
const dx = p.x - centerX; |
|
const dy = p.y - centerY; |
|
return { |
|
x: centerX + dx * Math.cos(angleRad) - dy * Math.sin(angleRad), |
|
y: centerY + dx * Math.sin(angleRad) + dy * Math.cos(angleRad) |
|
}; |
|
}); |
|
const minX = Math.min(...rotatedPoints.map((p) => p.x)); |
|
const maxX = Math.max(...rotatedPoints.map((p) => p.x)); |
|
const minY = Math.min(...rotatedPoints.map((p) => p.y)); |
|
const maxY = Math.max(...rotatedPoints.map((p) => p.y)); |
|
const shadowOffset = Math.abs(shadowX) + shadowBlur; |
|
const canvasWidth = Math.ceil(maxX - minX + shadowOffset * 2); |
|
const canvasHeight = Math.ceil(maxY - minY + shadowOffset * 2); |
|
const offsetX = -minX + shadowOffset + (shadowX < 0 ? -shadowX : 0); |
|
const offsetY = -minY + shadowOffset + (shadowY < 0 ? -shadowY : 0); |
|
statusEl.textContent = "Création du canvas..."; |
|
const canvas = document.createElement("canvas"); |
|
canvas.width = canvasWidth; |
|
canvas.height = canvasHeight; |
|
const ctx = canvas.getContext("2d"); |
|
if (shadowBlur > 0 || shadowX !== 0 || shadowY !== 0) { |
|
ctx.shadowColor = "rgba(0,0,0,0.5)"; |
|
ctx.shadowOffsetX = shadowX; |
|
ctx.shadowOffsetY = shadowY; |
|
ctx.shadowBlur = shadowBlur; |
|
} |
|
statusEl.textContent = "Application des transformations..."; |
|
ctx.translate(offsetX, offsetY); |
|
ctx.translate(centerX, centerY); |
|
ctx.rotate(angleRad); |
|
ctx.transform(1, Math.tan(skewYRad), Math.tan(skewXRad), 1, 0, 0); |
|
ctx.scale(scale, scale); |
|
ctx.translate(-imgWidth / 2, -imgHeight / 2); |
|
ctx.drawImage(image, 0, 0, imgWidth, imgHeight); |
|
ctx.setTransform(1, 0, 0, 1, 0, 0); |
|
statusEl.textContent = "Application des filtres..."; |
|
const filters = []; |
|
effects.forEach((effect) => { |
|
const slider = document.getElementById(effect.property); |
|
if (slider && enabledEffects[effect.property]) { |
|
const value = parseFloat(slider.value); |
|
if (value !== effect.default) { |
|
switch (effect.type) { |
|
case "filter": |
|
if (effect.property === "opacity") { |
|
filters.push(`opacity(${value / 100})`); |
|
} else if (effect.property === "hue-rotate") { |
|
filters.push(`hue-rotate(${value}deg)`); |
|
} else { |
|
filters.push(`${effect.property}(${value}${effect.unit})`); |
|
} |
|
break; |
|
case "neon": |
|
if (value > 0) { |
|
const neonIntensity = 1 + value / 50; |
|
filters.push(`brightness(${neonIntensity})`); |
|
filters.push(`saturate(${neonIntensity})`); |
|
} |
|
break; |
|
case "vintage": |
|
if (value > 0) { |
|
const v = value / 100; |
|
filters.push(`sepia(${0.3 + v * 0.7})`); |
|
filters.push(`contrast(${1 + v * 0.5})`); |
|
filters.push(`brightness(${0.9 + v * 0.2})`); |
|
if (value > 50) filters.push(`blur(0.5px)`); |
|
} |
|
break; |
|
case "sunset": |
|
if (value > 0) { |
|
const s = value / 100; |
|
filters.push(`sepia(${0.3 + s * 0.5})`); |
|
filters.push(`hue-rotate(${-10 + s * 20}deg)`); |
|
filters.push(`saturate(${1.2 + s})`); |
|
filters.push(`brightness(${1 + s * 0.3})`); |
|
} |
|
break; |
|
case "frost": |
|
if (value > 0) { |
|
const f = value / 100; |
|
filters.push(`grayscale(${0.2 + f * 0.5})`); |
|
filters.push(`brightness(1.2)`); |
|
filters.push(`contrast(${1 - f * 0.3})`); |
|
filters.push(`blur(${f * 2}px)`); |
|
filters.push(`sepia(0.2)`); |
|
filters.push(`hue-rotate(180deg)`); |
|
} |
|
break; |
|
case "fireworks": |
|
if (value > 0) { |
|
filters.push(`brightness(1.5)`); |
|
filters.push(`saturate(3)`); |
|
filters.push(`contrast(1.2)`); |
|
} |
|
break; |
|
} |
|
} |
|
} |
|
}); |
|
if (filters.length > 0) { |
|
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); |
|
const filterCanvas = document.createElement("canvas"); |
|
filterCanvas.width = canvas.width; |
|
filterCanvas.height = canvas.height; |
|
const filterCtx = filterCanvas.getContext("2d"); |
|
filterCtx.filter = filters.join(" "); |
|
filterCtx.drawImage(canvas, 0, 0); |
|
ctx.clearRect(0, 0, canvas.width, canvas.height); |
|
ctx.drawImage(filterCanvas, 0, 0); |
|
} |
|
if (borderRadius > 0) { |
|
statusEl.textContent = "Application des bordures arrondies..."; |
|
const roundedCanvas = document.createElement("canvas"); |
|
roundedCanvas.width = canvas.width; |
|
roundedCanvas.height = canvas.height; |
|
const roundedCtx = roundedCanvas.getContext("2d"); |
|
roundedCtx.beginPath(); |
|
roundedCtx.roundRect(0, 0, canvas.width, canvas.height, borderRadius); |
|
roundedCtx.closePath(); |
|
roundedCtx.clip(); |
|
roundedCtx.drawImage(canvas, 0, 0); |
|
ctx.clearRect(0, 0, canvas.width, canvas.height); |
|
ctx.drawImage(roundedCanvas, 0, 0); |
|
} |
|
statusEl.textContent = "Génération du fichier..."; |
|
canvas.toBlob( |
|
(blob) => { |
|
const url = URL.createObjectURL(blob); |
|
const link = document.createElement("a"); |
|
link.href = url; |
|
link.download = `image-effets-${Date.now()}.png`; |
|
document.body.appendChild(link); |
|
link.click(); |
|
document.body.removeChild(link); |
|
URL.revokeObjectURL(url); |
|
statusEl.textContent = "Téléchargement terminé !"; |
|
setTimeout(() => { |
|
document.body.removeChild(loadingOverlay); |
|
}, 1500); |
|
}, |
|
"image/png", |
|
1.0 |
|
); |
|
} catch (err) { |
|
console.error("Erreur export:", err); |
|
const statusEl = document.getElementById("export-status"); |
|
if (statusEl) { |
|
statusEl.textContent = "Erreur: " + err.message; |
|
statusEl.style.color = "#ff4444"; |
|
} |
|
setTimeout(() => { |
|
if (loadingOverlay.parentNode) { |
|
document.body.removeChild(loadingOverlay); |
|
} |
|
}, 3000); |
|
} finally { |
|
exportBtn.disabled = !1; |
|
exportBtn.style.opacity = "1"; |
|
exportBtn.innerHTML = "Export"; |
|
} |
|
} |
|
exportBtn.addEventListener("click", exportImage); |