const CDN_URL = 'https://cdn.jsdelivr.net/npm/@imgly/background-removal@1.7.0/dist/index.mjs' // eslint-disable-next-line @typescript-eslint/no-explicit-any let _removeBg: ((blob: Blob, opts: Record) => Promise) | null = null async function loadLib() { if (_removeBg) return _removeBg const mod = await import(/* webpackIgnore: true */ CDN_URL) _removeBg = mod.removeBackground return _removeBg! } export interface ProgressEvent { key: string percent: number } /** * Supprime le fond d'un Blob image via @imgly IA. * Retourne un PNG avec fond transparent (à combiner avec compositeOnWhite). */ export async function removeBackgroundRaw( blob: Blob, onProgress?: (e: ProgressEvent) => void, ): Promise { const removeBg = await loadLib() return removeBg(blob, { output: { format: 'image/png', type: 'foreground', quality: 1 }, progress: (key: string, current: number, total: number) => { if (onProgress && total > 0) { onProgress({ key, percent: Math.round((current / total) * 100) }) } }, }) } /** * Composite un PNG transparent sur fond blanc avec feather optionnel. * @param pngBlob — PNG avec transparence (résultat de removeBackgroundRaw) * @param feather — récupération des bords en px (0 = strict, 5 = doux) */ export function compositeOnWhite(pngBlob: Blob, feather: number): Promise { const objectUrl = URL.createObjectURL(pngBlob) return new Promise((resolve, reject) => { const img = new Image() img.onload = () => { const { width, height } = img const fgCanvas = document.createElement('canvas') fgCanvas.width = width fgCanvas.height = height const fgCtx = fgCanvas.getContext('2d')! fgCtx.drawImage(img, 0, 0) if (feather > 0) { fgCtx.globalCompositeOperation = 'destination-over' fgCtx.filter = `blur(${feather}px)` fgCtx.drawImage(img, 0, 0) fgCtx.filter = 'none' fgCtx.globalCompositeOperation = 'source-over' } // Canvas de sortie 1000×1000 blanc, sujet centré avec padding de 5% const SIZE = 1000 const PADDING = 0.05 const available = Math.round(SIZE * (1 - PADDING * 2)) const scale = Math.min(available / width, available / height) const dw = Math.round(width * scale) const dh = Math.round(height * scale) const dx = Math.round((SIZE - dw) / 2) const dy = Math.round((SIZE - dh) / 2) const out = document.createElement('canvas') out.width = SIZE out.height = SIZE const ctx = out.getContext('2d')! ctx.fillStyle = '#ffffff' ctx.fillRect(0, 0, SIZE, SIZE) ctx.drawImage(fgCanvas, dx, dy, dw, dh) URL.revokeObjectURL(objectUrl) out.toBlob( (b) => (b ? resolve(b) : reject(new Error('canvas.toBlob a retourné null'))), 'image/jpeg', 0.92, ) } img.onerror = () => { URL.revokeObjectURL(objectUrl) reject(new Error('Impossible de charger le PNG')) } img.src = objectUrl }) }