/** * Convertit un Blob HEIC/HEIF en Blob JPEG. * Tente d'abord la conversion native du navigateur (instantané sur Safari/macOS), * puis repasse sur heic2any si le natif échoue (Chrome, Firefox). * À utiliser uniquement côté client (navigateur). */ export async function convertHeicToJpeg(blob: Blob): Promise { if (!(blob instanceof Blob)) { throw new TypeError("L'entrée doit être un Blob") } const type = blob.type.toLowerCase() if (type !== '' && !['image/heic', 'image/heif'].includes(type)) { throw new TypeError(`Format attendu : HEIC/HEIF. Reçu : ${blob.type || '(vide)'}`) } // Méthode 1 : conversion native (Safari 14+, macOS Chrome 105+) try { const result = await convertNative(blob) console.log('[heicConverter] ✅ Conversion native réussie') return result } catch (e) { console.warn(`[heicConverter] Conversion native échouée (${(e as Error).message}), repasse sur heic2any…`) } // Méthode 2 : conversion serveur via /api/images/convert-heic (sharp, fiable sur tous les navigateurs) try { const result = await convertViaServer(blob) console.log('[heicConverter] ✅ Conversion serveur réussie') return result } catch (e) { console.warn(`[heicConverter] Conversion serveur échouée (${(e as Error).message}), repasse sur heic2any…`) } // Méthode 3 : heic2any fallback (plus lent, ~10-30s) const heic2any = (await import('heic2any')).default const timeout = new Promise((_, reject) => setTimeout( () => reject(new Error('Timeout conversion HEIC (30s). Essayez de convertir le fichier en JPG avant de le déposer.')), 30_000, ), ) const conversion = heic2any({ blob, toType: 'image/jpeg', quality: 0.92 }) const result = await Promise.race([conversion, timeout]) const output = Array.isArray(result) ? result[0] : result if (!(output instanceof Blob)) { throw new Error("heic2any n'a pas retourné un Blob") } console.log('[heicConverter] ✅ Conversion heic2any réussie') return output } async function convertNative(blob: Blob): Promise { const typed = blob.type ? blob : new Blob([blob], { type: 'image/heic' }) // Tentative 1 : createImageBitmap (Safari 14+) try { const bitmap = await createImageBitmap(typed) const canvas = document.createElement('canvas') canvas.width = bitmap.width canvas.height = bitmap.height canvas.getContext('2d')!.drawImage(bitmap, 0, 0) bitmap.close() return await canvasToBlob(canvas) } catch { // Pas supporté — essai suivant } // Tentative 2 : + canvas (Chrome sur macOS — utilise le décodeur HEIC natif de macOS) return new Promise((resolve, reject) => { const url = URL.createObjectURL(typed) const img = new Image() img.onload = () => { const canvas = document.createElement('canvas') canvas.width = img.naturalWidth canvas.height = img.naturalHeight canvas.getContext('2d')!.drawImage(img, 0, 0) URL.revokeObjectURL(url) canvasToBlob(canvas).then(resolve).catch(reject) } img.onerror = () => { URL.revokeObjectURL(url) reject(new Error('Décodage HEIC non supporté par ce navigateur')) } img.src = url }) } async function convertViaServer(blob: Blob): Promise { const formData = new FormData() const file = blob instanceof File ? blob : new File([blob], 'image.heic', { type: 'image/heic' }) formData.append('file', file) const res = await fetch('/api/images/convert-heic', { method: 'POST', body: formData }) if (!res.ok) { const err = await res.json().catch(() => ({ error: `HTTP ${res.status}` })) throw new Error(err.error ?? `HTTP ${res.status}`) } return res.blob() } function canvasToBlob(canvas: HTMLCanvasElement): Promise { return new Promise((resolve, reject) => { canvas.toBlob( b => (b ? resolve(b) : reject(new Error('canvas.toBlob a retourné null'))), 'image/jpeg', 0.92, ) }) }