feat: utilitaires client heicConverter, bgRemover, imageRotator, zipImporter (TS)
Porte les 4 utilitaires de traitement d'images en TypeScript pour utilisation côté client : - heicConverter.ts : convertit HEIC/HEIF en JPEG via heic2any - bgRemover.ts : supprime les fonds d'images avec @imgly/background-removal + composite sur blanc - imageRotator.ts : pivote les images JPEG via Canvas - zipImporter.ts : extrait les images d'un fichier ZIP avec structure dossier-ref/image.ext Tous les modules utilisent des APIs du navigateur et doivent être importés dynamiquement depuis du code 'use client'. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
const CDN_VERSION = '1.7.0'
|
||||
const CDN_URL = `https://cdn.jsdelivr.net/npm/@imgly/background-removal@${CDN_VERSION}/dist/index.mjs`
|
||||
|
||||
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'
|
||||
}
|
||||
|
||||
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 = () => {
|
||||
URL.revokeObjectURL(objectUrl)
|
||||
reject(new Error('Impossible de charger le PNG'))
|
||||
}
|
||||
img.src = objectUrl
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user