Technický článek

Vkládání obrázků AVIF, HEIF a JPEG XL do PDF z Delphi

PDF Library for Delphi přijímá jako vstup obrázky AVIF, HEIF a JPEG XL přes AddModernImageFromFile a jeho varianty pro proud a řetězec, přičemž na cestě do objektu obrázku PDF zachovává alfa kanál, vložený profil ICC a 16bitové kanály. Rozpoznání formátu probíhá omezeným čtením magického čísla a dekódování běží přes vyměnitelný backend, takže pro soubor, který ve skutečnosti není žádným z těchto formátů, se nevolá nic externího

Tyto formáty se do dokumentových pracovních postupů dostaly přes telefony. iOS produkuje HEIC ve výchozím nastavení už léta, zařízení Android produkují AVIF a technik v terénu, který fotí poškozený díl, pošle obrázek, který generátor sestav PDF postavený v roce 2015 vůbec neumí otevřít. Obecná záchranná cesta, dekódování přes systémovou bitmapu, spolehlivě vydá 8bitovou barvu a cestou ztratí alfa kanál i barevný profil

Co moderní cesta pro obrázky zachovává, co při převodu na bitmapu zaniká?

Tři věci, a každá má pracovní postup, který na ní závisí. Alfa kanál přežije, což má význam pro loga a produktové výřezy skládané nad obsah stránky. Profil ICC přežije, což má význam pro cokoli, co se bude tisknout nebo barevně srovnávat. A 16bitové kanály přežijí, což má význam pro lékařské a vědecké snímky, kde 8bitová kvantizace ničí právě ty odstíny, kvůli kterým byl obrázek pořízen

Průchod obrázku systémovou bitmapou ztratí všechny tři v jednom kroku, a dělá to potichu: výsledné PDF vypadá přibližně správně a nikdo si toho nevšimne, dokud se tiskárna nezeptá, proč je firemní červená špatně. Hodnota volby 8 u moderních volání pro obrázky je příznak, který drží alfa kanál, ICC a 16bitové kanály pohromadě, a je pro tato volání výchozí

Diagram PDF Library for Delphi porovnávající převod platformní bitmapy, který zahodí alfa kanál, ICC profil a 16bitové kanály, s moderní cestou obrázku, jež přenese všechny tři do objektu PDF obrázku
Obecné dekódování bitmapy potichu zploští průhlednost a kvantuje kanály dřív, než se stránka vůbec vykreslí

Přidání obrázku na stránku

Volání vrátí identifikátor obrázku, který se pak vybere a nakreslí, nebo nakreslí a uvolní v jednom kroku:

uses
  PDFlibrary, PDFlibModernImage;

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

    // Options = 8 zachová alfa kanál, ICC a 16bitové kanály
    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;

Rozpoznávání předchází dekódování a je záměrně úzké. Knihovna přečte omezenou hlavičku, rozpozná značky formátu ISO base media file, které identifikují AVIF a HEIF, a rozpozná jak syrové, tak kontejnerové signatury JPEG XL, poté obnoví pozici volajícího proudu. Neznámý nebo maskovaný vstup se k externímu kodeku nikdy nedostane, což brání tomu, aby byl přejmenovaný spustitelný soubor předán dekodéru, jako by šlo o obrázek

Kde se dekódování ve skutečnosti odehrává?

Moderní formáty obrázků jsou velké, složité kodeky a vložit jeden z nich přímo do knihovny PDF by byla podivná návrhová volba. Výchozí backend dynamicky načte nasaditelný modul MagickWand v rámci procesu a hledá jej v zdokumentovaném pořadí: explicitní soubor nebo adresář, který nastavíte, proměnné prostředí, adresář spustitelného souboru a systémová cesta pro vyhledávání

Diagram ohraničené brány detekce magických čísel potvrzující AVIF, HEIF a JPEG XL před výběrem dekódovacího backendu z explicitní cesty, proměnných prostředí, adresáře spustitelného souboru nebo registrovaného callbacku Delphi
Detekce pracuje na omezeném čtení hlavičky, takže přejmenovaný spustitelný soubor se ke kodeku nikdy nedostane

Aplikace, které už dodávají vlastní dekodér, nebo které nesmí vůbec načítat externí modul, místo toho registrují vlastní zpětné volání. Smlouva je malá: přečíst vstupní proud, zapsat PNG do výstupního proudu, respektovat požadovanou orientaci:

function MyDecoder(InStream, OutPNG: TStream;
  ImageFormat: TPDFlibModernImageFormat;
  ApplyOrientation: Boolean): Boolean;
begin
  // Dekódujte InStream vlastním kodekem a zapište bajty PNG do OutPNG
  Result := DecodeWithBundledCodec(InStream, OutPNG,
    ImageFormat, ApplyOrientation);
end;

begin
  RegisterModernImageDecoderBackend(MyDecoder);
  // ... přidání obrázků ...
  ClearModernImageDecoderBackend;    // zpět na výchozí backend
end;

Nasazení má jednu výhodu a jedno záměrné omezení. Pokud adresář kodeku obsahuje podadresář modules\coders, knihovna doplní proměnné prostředí kodeku, které takové rozvržení potřebuje, ale pouze pokud je hostitelská aplikace ještě sama nenastavila. Aplikace s vlastní strategií nasazení runtime si ji ponechává

Proč je uprostřed PNG?

Přemostění přes PNG v paměti místo přes syrový buffer pixelů vypadá jako krok navíc a ve skutečnosti je to nejlevnější správný krok. PNG vyjadřuje vše, co musí přežít, alfa kanál, typ barvy, bitovou hloubku a vložený profil ICC, a knihovna už má vyzrálou, dobře otestovanou cestu z PNG do objektu obrázku PDF se správnými filtry a barevným prostorem. Její opětovné použití znamená, že moderní formáty zdědí roky práce na správnosti místo toho, aby dostaly paralelní implementaci

Přemostění je zcela v paměti, takže se nevytvářejí žádné dočasné soubory a při pádu není potřeba žádný úklid. Jedna vráska vyžadovala výslovné ošetření: některé konverze při změně formátu zahodí profil ICC. Backend proto zachytí zdrojový profil ještě před přepnutím formátu, zkomprimuje jej pomocí Flate, sestaví platný blok iCCP s přepočítaným CRC a odstraní jakýkoli blok sRGB, který by s ním kolidoval. Při testování si dekódovaný AVIF ponechal 16bitové RGBA s 16bitovým alfa kanálem a profil extrahovaný z výsledného PDF odpovídal zdrojovému profilu bajt po bajtu na 60 960 bajtech

Diagram PDF Library for Delphi: PNG most v paměti zachytí zdrojový ICC profil před změnou formátu, znovu ho sestaví jako komprimovaný chunk iCCP, odstraní konfliktní chunk sRGB a znovu použije ověřenou cestu vkládání PNG do PDF
Záloha ICC profilu před změnou formátu udrží vloženou kopii bitově shodnou se zdrojem

Praktické poznámky před nasazením do produkce

Dostupnost kontrolujte při startu, ne až u první fotografie. ModernImageCodecAvailable hlásí, zda lze backend použít, a SetModernImageCodecLibrary ukazuje na explicitní soubor nebo adresář, když vaše nasazení umístí kodek na nestandardní místo:

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

Sledujte velikost souboru výsledku. 16bitový obrázek RGBA s vloženým profilem je velký objekt obrázku PDF a sestava se čtyřiceti takovými bude velká. Když je dokument určen pro zobrazení na obrazovce, ne pro tisk, je podvzorkování před vložením správným kompromisem, a obecné páky pro velikost popisuje článek optimalizace velikosti souboru PDF

Nakonec rozhodněte o barevné politice záměrně. Ponechání zdrojového profilu je správné pro archivaci a tiskovou práci; převod na prostor jednotný pro celý dokument je správný, když musí smíšená sada fotografií vypadat konzistentně, a cestu konverze popisuje článek přebarvení dokumentu do jiného barevného prostoru. Pokud potřebujete potvrdit, co se v souboru skutečně ocitlo, cesta inspekce v článku extrakce textu, obrázků a fontů hlásí objekty obrázků, které dokument nese

Vstup moderních obrázků, správa barev a optimalizace obrázků jsou součástí stejné knihovny pro Delphi, C++Builder a Free Pascal; kompletní seznam funkcí je na stránce PDF Library for Delphi