/** * Открытие загруженных картинок через sharp с поддержкой HEIC. * HEIC (фото с iPhone) — HEIF с кодеком HEVC. Сборка libvips в sharp декодирует только * AV1-HEIF (AVIF), поэтому HEIC разбираем через libheif-js (WASM) и отдаём sharp сырые пиксели. */ const fs = require('fs'); const sharp = require('sharp'); const heicDecode = require('heic-decode'); const HEVC_BRANDS = ['heic', 'heix', 'hevc', 'hevx', 'heim', 'heis', 'hevm', 'hevs']; // file — объект multer ({ path, originalname, mimetype }) function isHeic(file) { let brand = ''; try { const fd = fs.openSync(file.path, 'r'); const head = Buffer.alloc(12); fs.readSync(fd, head, 0, 12, 0); fs.closeSync(fd); if (head.toString('latin1', 4, 8) === 'ftyp') brand = head.toString('latin1', 8, 12); } catch {} if (HEVC_BRANDS.includes(brand)) return true; if (brand === 'avif' || brand === 'avis') return false; return /\.(heic|heif)$/i.test(file.originalname) || /^image\/hei[cf]/.test(file.mimetype); } async function openImage(file) { if (!isHeic(file)) return sharp(file.path); const { width, height, data } = await heicDecode({ buffer: await fs.promises.readFile(file.path) }); const image = sharp(Buffer.from(data.buffer, data.byteOffset, data.byteLength), { raw: { width, height, channels: 4 } }); // libheif всегда отдаёт RGBA; у фото альфа сплошь 255 — без неё не будет лишнего PNG/альфа-канала // (пересобираем буфер: metadata() описывает вход, и removeAlpha() в конвейере его hasAlpha не сбросит) for (let i = 3; i < data.length; i += 4) if (data[i] !== 255) return image; const rgb = await image.removeAlpha().raw().toBuffer(); return sharp(rgb, { raw: { width, height, channels: 3 } }); } module.exports = { isHeic, openImage };