wa-dev-tools/lib/image.js
treamz c65b3f5006 HEIC в /compress, конструктор «Картинки → PDF», белый фон зон загрузки и табов
HEIC:
- lib/image.js — openImage(): HEIC (HEVC) через heic-decode (libheif WASM), т.к. sharp
  декодирует только AV1-HEIF; непрозрачная альфа отбрасывается
- compress: общий encodeImage(); формат «оригинала» по содержимому (metadata), а не по MIME;
  HEIC/BMP в режиме «Оригинал» → JPEG с расширением .jpg

Картинки → PDF (/pdf):
- /pdf/fromImages переписан: любые форматы вкл. HEIC, порядок/повторы/поворот страниц,
  размер листа (по картинке/A4/A3/A5/Letter), ориентация, поля, качество, имя файла;
  повторы встраиваются один раз; нетронутые JPEG/PNG — байт-в-байт
- /pdf/thumb — серверные миниатюры для HEIC/TIFF (свой мягкий лимитер)
- UI-конструктор: сетка с превью, drag&drop, сортировка, массовый поворот, прогресс загрузки
- старая вкладка была нерабочей (поле files вместо images) и требовала загрузки PDF
- SVG не принимается (librsvg + внешние ссылки)
- фикс: потерянная обёртка panel-watermark ломала вложенность stepConfig
- фикс: pdfIcon недоступен из inline onerror

UI: белый фон (--surface-800) у зон загрузки, неактивных табов, опций и карточек
(compress, pdf, video, converter, formatter, sanitizer, parser)
2026-09-22 20:37:48 +03:00

40 lines
1.9 KiB
JavaScript
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.

/**
* Открытие загруженных картинок через 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 };