Технічна стаття

Вбудовування зображень AVIF, HEIF і JPEG XL у PDF з Delphi

PDF Library for Delphi приймає зображення AVIF, HEIF і JPEG XL як вхідні дані через AddModernImageFromFile та його потокові й рядкові варіанти, зберігаючи альфа-канал, вбудований ICC-профіль і 16-бітові канали на шляху в об'єкт зображення PDF. Виявлення формату відбувається за обмеженим зчитуванням магічного числа, а декодування проходить через замінний бекенд, тож для файлу, який насправді не є жодним з цих форматів, нічого зовнішнього не викликається

Ці формати потрапили в робочі процеси документів через телефони. iOS роками створює HEIC за замовчуванням, пристрої Android створюють AVIF, а польовий технік, що фотографує пошкоджену деталь, надсилає зображення, яке генератор звітів PDF, побудований 2015 року, взагалі не може відкрити. Загальний резервний шлях, декодування через платформове растрове зображення, надійно дає 8-бітовий колір і втрачає по дорозі альфа-канал і колірний профіль

Що зберігає сучасний шлях зображення, який втрачає конвертація в растрове зображення?

Три речі, і кожна має робочий процес, що від неї залежить. Альфа-канал виживає, що важливо для логотипів і вирізок продуктів, накладених на вміст сторінки. ICC-профіль виживає, що важливо для всього, що буде надруковано чи узгоджено за кольором. І 16-бітові канали виживають, що важливо для медичних і наукових зображень, де 8-бітове квантування руйнує саме ті градації, заради яких зображення знімалося

Пропускання зображення через платформове растрове зображення втрачає всі три за один крок, і робить це мовчки: результуючий PDF виглядає приблизно правильно, і ніхто цього не помічає, доки друкар не запитає, чому корпоративний червоний неправильний. Значення параметра 8 у викликах сучасних зображень — це прапорець, що зберігає альфа-канал, ICC і 16-бітові канали разом, і він типовий для цих викликів

Додавання зображення на сторінку

Виклик повертає ідентифікатор зображення, який потім обирається й малюється, або малюється й звільняється за один крок:

uses
  PDFlibrary, PDFlibModernImage;

var
  Lib: TPDFlib;
  ImageID: Integer;
begin
  Lib := TPDFlib.Create;
  try
    Lib.NewDocument;
    Lib.SetPageSize('A4');
    Lib.NewPage;

    // Options = 8 зберігає альфа-канал, ICC і 16-бітові канали
    ImageID := Lib.AddModernImageFromFile('site-photo.heic', 8);
    if ImageID > 0 then
      Lib.DrawImageAndRelease(ImageID, 40, 40, 515, 340)
    else
      Lib.DrawText(40, 40, 'image could not be decoded');

    Lib.SaveToFile('inspection-report.pdf');
  finally
    Lib.Free;
  end;
end;

Виявлення передує декодуванню й свідомо вузьке. Бібліотека читає обмежений заголовок, розпізнає бренди базового формату медіафайлів ISO, що ідентифікують AVIF і HEIF, і розпізнає і необроблену, і контейнерну сигнатури JPEG XL, а потім відновлює позицію потоку викликача. Невідомий чи замаскований вхід ніколи не досягає зовнішнього кодека, що не дає перейменованому виконуваному файлу потрапити в декодер, ніби це картинка

Де насправді відбувається декодування?

Сучасні формати зображень — це великі, складні кодеки, і вбудовувати один з них у бібліотеку PDF було б дивним рішенням дизайну. Типовий бекенд динамічно завантажує розгортаний модуль MagickWand у власному процесі й шукає його в задокументованому порядку: явний файл чи каталог, який ви встановили, змінні середовища, каталог виконуваного файлу та системний шлях пошуку

Застосунки, що вже постачають декодер, або яким взагалі заборонено завантажувати зовнішній модуль, натомість реєструють власний зворотний виклик. Контракт невеликий: прочитати вхідний потік, записати PNG у вихідний потік, врахувати запитану орієнтацію:

function MyDecoder(InStream, OutPNG: TStream;
  ImageFormat: TPDFlibModernImageFormat;
  ApplyOrientation: Boolean): Boolean;
begin
  // Декодуйте InStream власним кодеком і запишіть байти PNG в OutPNG
  Result := DecodeWithBundledCodec(InStream, OutPNG,
    ImageFormat, ApplyOrientation);
end;

begin
  RegisterModernImageDecoderBackend(MyDecoder);
  // ... додайте зображення ...
  ClearModernImageDecoderBackend;    // повернення до типового бекенда
end;

Розгортання отримує одну зручність і одне свідоме обмеження. Якщо каталог кодека містить підкаталог modules\coders, бібліотека заповнює змінні середовища кодека, які потрібні такому компонуванню, але лише коли застосунок-хост їх ще не встановив. Застосунок з власною стратегією розгортання середовища виконання зберігає її

Чому PNG посередині?

Проміжна ланка через PNG у пам'яті, а не через необроблений піксельний буфер, виглядає як зайвий крок, а насправді є найдешевшим правильним рішенням. PNG виражає все, що має вижити: альфа-канал, тип кольору, бітову глибину та вбудований ICC-профіль, і бібліотека вже має зрілий, добре перевірений шлях з PNG у об'єкт зображення PDF з правильними фільтрами й колірним простором. Повторне використання цього означає, що сучасні формати успадковують роки роботи над коректністю замість отримання паралельної реалізації

Проміжна ланка повністю в пам'яті, тож не створюється жодних тимчасових файлів і не потрібне прибирання після збою. Одна складність потребувала явної обробки: деякі конвертації відкидають ICC-профіль при зміні формату. Тому бекенд захоплює вихідний профіль до перемикання формату, стискає його Flate, будує коректний блок iCCP з перерахованою контрольною сумою CRC і видаляє будь-який блок sRGB, що конфліктував би з ним. У тестуванні декодований AVIF зберіг 16-бітовий RGBA з 16-бітовим альфа-каналом, а профіль, вилучений з результуючого PDF, збігся з вихідним профілем побайтово при 60 960 байтах

Практичні примітки перед увімкненням у продакшні

Перевіряйте доступність під час запуску, а не на першій фотографії. ModernImageCodecAvailable повідомляє, чи можна використовувати бекенд, а SetModernImageCodecLibrary вказує на явний файл чи каталог, коли ваше розгортання розміщує кодек десь нестандартно:

Lib.SetModernImageCodecLibrary('C:\MyApp\codecs');
if Lib.ModernImageCodecAvailable = 0 then
  Log('modern image input unavailable - HEIC and AVIF will be refused');

Слідкуйте за розміром файлу результату. 16-бітове зображення RGBA з вбудованим профілем — це великий об'єкт зображення PDF, і звіт із сорока такими буде великим. Коли документ призначено для перегляду на екрані, а не для друку, зменшення дискретизації перед вбудовуванням — правильний компроміс, і загальні важелі розміру описано у статті оптимізація розміру файлу PDF

Нарешті, вирішуйте політику кольору свідомо. Збереження вихідного профілю правильне для архівної та друкарської роботи; конвертація в простір, спільний для всього документа, правильна, коли змішаний набір фотографій має виглядати узгоджено, і шлях конвертації описано у статті перефарбування документа в інший колірний простір. Якщо вам потрібно підтвердити, що насправді потрапило у файл, шлях огляду у статті вилучення тексту, зображень і шрифтів повідомляє об'єкти зображень, які несе документ

Вхід сучасних зображень, керування кольором і оптимізація зображень — частини тієї самої бібліотеки для Delphi, C++Builder і Free Pascal; повний перелік можливостей на сторінці PDF Library for Delphi