Files
gestion-materiaux-destock/src/lib/client/bgRemover.ts
T

99 lines
3.0 KiB
TypeScript
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.
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<string, unknown>) => Promise<Blob>) | 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<Blob> {
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<Blob> {
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
})
}