Files
convertisseur-images/js/bgRemover.js
T
C_Ma_For 86d9b351b1 feat: ZIP import, rotation, feather slider, lightbox + config déploiement
- Import ZIP produits (HEIC/JPG/PNG, structure dossiers conservée)
- Rotation des images ±90° avec aperçu CSS et export canvas
- Slider récupération des bords (feather 0–5 px)
- Lightbox plein écran (clic miniature, Échap, fond noir)
- Fix timeout HEIC 30s → 120s
- Fix MIME type blobs JSZip pour heic2any
- Dockerfile nginx:alpine + docker-compose.yml pour déploiement

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-06 11:18:18 +02:00

102 lines
3.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Suppression de fond par IA — @imgly/background-removal (WebAssembly).
* Le modèle est téléchargé depuis staticimgly.com au premier lancement.
*/
const CDN_VERSION = '1.7.0';
const CDN = `https://cdn.jsdelivr.net/npm/@imgly/background-removal@${CDN_VERSION}/dist/`;
let _removeBg = null;
async function loadLib() {
if (_removeBg) return _removeBg;
const mod = await import(CDN + 'index.mjs');
_removeBg = mod.removeBackground;
return _removeBg;
}
/**
* Supprime le fond d'un Blob image via IA puis composite sur blanc.
* @param {Blob} blob — image source (JPEG)
* @param {function} [onProgress] — callback({ key, percent })
* @param {number} [feather=2] — récupération de bords en px (0 = strict, 5 = doux)
* @returns {Promise<Blob>} Blob JPEG fond blanc
*/
export async function removeBackground(blob, onProgress, feather = 2) {
const removeBg = await loadLib();
const pngBlob = await removeBg(blob, {
// publicPath omis → utilise staticimgly.com par défaut
output: {
format: 'image/png',
type: 'foreground',
quality: 1,
},
progress: (key, current, total) => {
if (onProgress && total > 0) {
onProgress({ key, percent: Math.round((current / total) * 100) });
}
},
});
return compositeOnWhite(pngBlob, feather);
}
/**
* Composite un PNG transparent sur fond blanc.
* Si feather > 0, dilate légèrement le contour du sujet (récupère les bords
* que l'IA a pu couper trop agressivement).
* @param {Blob} pngBlob
* @param {number} feather — rayon de dilation en pixels (05)
* @returns {Promise<Blob>} JPEG fond blanc
*/
async function compositeOnWhite(pngBlob, feather) {
const objectUrl = URL.createObjectURL(pngBlob);
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => {
const { width, height } = img;
// ── Canvas intermédiaire : premier plan avec bords optionnels ──
const fgCanvas = document.createElement('canvas');
fgCanvas.width = width;
fgCanvas.height = height;
const fgCtx = fgCanvas.getContext('2d');
// 1. Dessin net du premier plan
fgCtx.drawImage(img, 0, 0);
if (feather > 0) {
// 2. On dessine une version floutée DERRIÈRE le dessin net
// (destination-over = "derrière le contenu existant")
// → les zones transparentes proches des bords du sujet deviennent
// semi-transparentes, récupérant les détails coupés par l'IA.
fgCtx.globalCompositeOperation = 'destination-over';
fgCtx.filter = `blur(${feather}px)`;
fgCtx.drawImage(img, 0, 0);
fgCtx.filter = 'none';
fgCtx.globalCompositeOperation = 'source-over';
}
// ── Canvas final : fond blanc + premier plan ───────────────────
const out = document.createElement('canvas');
out.width = width;
out.height = height;
const ctx = out.getContext('2d');
ctx.fillStyle = '#ffffff';
ctx.fillRect(0, 0, width, height);
ctx.drawImage(fgCanvas, 0, 0);
URL.revokeObjectURL(objectUrl);
out.toBlob(b => {
b ? resolve(b) : reject(new Error('canvas.toBlob a retourné null'));
}, 'image/jpeg', 0.92);
};
img.onerror = () => reject(new Error('Impossible de charger le résultat IA'));
img.src = objectUrl;
});
}