Artykuł techniczny

Osadzanie obrazów AVIF, HEIF i JPEG XL w PDF z Delphi

PDF Library for Delphi przyjmuje na wejściu obrazy AVIF, HEIF i JPEG XL za pomocą AddModernImageFromFile oraz jego wariantów strumieniowych i ciągu znaków, zachowując po drodze do obiektu obrazu PDF kanał alfa, osadzony profil ICC i 16-bitowe kanały. Wykrywanie formatu odbywa się na ograniczonym odczycie liczby magicznej, a dekodowanie przechodzi przez wymienialny backend, więc dla pliku, który w rzeczywistości nie jest żadnym z tych formatów, nic zewnętrznego nie zostaje wywołane

Te formaty trafiły do przepływów pracy z dokumentami za sprawą telefonów. iOS od lat domyślnie generuje HEIC, urządzenia z Androidem generują AVIF, a technik terenowy fotografujący uszkodzoną część wysyła obraz, którego generator raportów PDF zbudowany w 2015 roku w ogóle nie potrafi otworzyć. Ogólna ścieżka zapasowa, dekodująca przez mapę bitową platformy, niezawodnie daje kolor 8-bitowy i po drodze traci kanał alfa oraz profil koloru

Co zachowuje nowoczesna ścieżka obrazu, co traci konwersja przez mapę bitową?

Trzy rzeczy, a każda ma przepływ pracy, który od niej zależy. Kanał alfa przetrwa, co ma znaczenie dla logotypów i wycinków produktów komponowanych na treści strony. Profil ICC przetrwa, co ma znaczenie dla wszystkiego, co będzie drukowane albo dopasowywane kolorystycznie. A 16-bitowe kanały przetrwają, co ma znaczenie dla obrazowania medycznego i naukowego, gdzie kwantyzacja 8-bitowa niszczy właśnie te gradacje, dla których obraz został uchwycony

Przepuszczenie obrazu przez mapę bitową platformy traci wszystkie trzy naraz w jednym kroku, i robi to po cichu: wynikowy PDF wygląda w przybliżeniu poprawnie, i nikt tego nie zauważa, dopóki drukarnia nie zapyta, dlaczego firmowa czerwień jest zła. Wartość opcji 8 w wywołaniach nowoczesnych obrazów to flaga, która utrzymuje razem kanał alfa, ICC i kanały 16-bitowe, i jest domyślna dla tych wywołań

Dodawanie obrazu do strony

Wywołanie zwraca identyfikator obrazu, który następnie jest wybierany i rysowany, albo rysowany i zwalniany w jednym kroku:

uses
  PDFlibrary, PDFlibModernImage;

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

    // Options = 8 zachowuje kanał alfa, ICC i kanały 16-bitowe
    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;

Wykrywanie poprzedza dekodowanie i jest celowo wąskie. Biblioteka odczytuje ograniczony nagłówek, rozpoznaje znaczniki formatu ISO base media file format identyfikujące AVIF i HEIF, oraz rozpoznaje zarówno surową, jak i kontenerową sygnaturę JPEG XL, po czym przywraca pozycję strumienia wywołującego. Nieznane albo zamaskowane wejście nigdy nie dociera do zewnętrznego kodeka, co zapobiega przekazaniu przemianowanego pliku wykonywalnego do dekodera tak, jakby był obrazem

Gdzie faktycznie zachodzi dekodowanie?

Nowoczesne formaty obrazów to duże, złożone kodeki, a umieszczenie jednego z nich wewnątrz biblioteki PDF byłoby dziwną decyzją projektową. Domyślny backend dynamicznie wczytuje w procesie wdrażalny moduł MagickWand i szuka go w udokumentowanej kolejności: jawny plik albo katalog, który ustawisz, zmienne środowiskowe, katalog pliku wykonywalnego oraz systemowa ścieżka wyszukiwania

Aplikacje, które już dostarczają własny dekoder albo w ogóle nie mogą wczytywać zewnętrznego modułu, rejestrują zamiast tego własny callback. Kontrakt jest niewielki: odczytaj strumień wejściowy, zapisz PNG do strumienia wyjściowego, uwzględnij żądaną orientację:

function MyDecoder(InStream, OutPNG: TStream;
  ImageFormat: TPDFlibModernImageFormat;
  ApplyOrientation: Boolean): Boolean;
begin
  // Zdekoduj InStream własnym kodekiem i zapisz bajty PNG do OutPNG
  Result := DecodeWithBundledCodec(InStream, OutPNG,
    ImageFormat, ApplyOrientation);
end;

begin
  RegisterModernImageDecoderBackend(MyDecoder);
  // ... dodaj obrazy ...
  ClearModernImageDecoderBackend;    // powrót do domyślnego backendu
end;

Wdrożenie otrzymuje jedno ułatwienie i jedno celowe ograniczenie. Jeśli katalog kodeka zawiera podkatalog modules\coders, biblioteka wypełnia zmienne środowiskowe kodeka, jakich potrzebuje taki układ, ale tylko wtedy, gdy aplikacja hostująca jeszcze ich nie ustawiła. Aplikacja z własną strategią wdrażania w czasie działania zachowuje tę strategię

Dlaczego PNG pośrodku?

Pomostowanie przez PNG w pamięci zamiast surowego bufora pikseli wygląda na dodatkowy krok, a w rzeczywistości jest najtańszym poprawnym rozwiązaniem. PNG wyraża wszystko, co musi przetrwać: kanał alfa, typ koloru, głębię bitową i osadzony profil ICC, a biblioteka ma już dojrzałą, dobrze przetestowaną ścieżkę z PNG do obiektu obrazu PDF z właściwymi filtrami i przestrzenią koloru. Ponowne jej wykorzystanie oznacza, że nowoczesne formaty dziedziczą lata pracy nad poprawnością, zamiast dostawać równoległą implementację

Pomost działa w całości w pamięci, więc nie powstają pliki tymczasowe i nie jest potrzebne sprzątanie po awarii. Jeden szczegół wymagał jawnej obsługi: niektóre konwersje przy zmianie formatu porzucają profil ICC. Backend przechwytuje więc profil źródłowy przed zmianą formatu, kompresuje go Flate, buduje poprawny fragment iCCP z przeliczonym CRC i usuwa każdy fragment sRGB, który by z nim kolidował. W testach zdekodowany AVIF zachował 16-bitowy RGBA z 16-bitowym kanałem alfa, a profil wyodrębniony z wynikowego PDF zgadzał się z profilem źródłowym bit po bicie przy 60 960 bajtach

Praktyczne uwagi przed włączeniem na produkcji

Sprawdź dostępność przy starcie, a nie przy pierwszej fotografii. ModernImageCodecAvailable zgłasza, czy backend może zostać użyty, a SetModernImageCodecLibrary wskazuje jawny plik albo katalog, gdy twoje wdrożenie umieszcza kodek w niestandardowym miejscu:

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

Obserwuj rozmiar pliku wyniku. Obraz 16-bitowy RGBA z osadzonym profilem to duży obiekt obrazu PDF, a raport z czterdziestoma takimi obrazami będzie duży. Gdy dokument jest przeznaczony do wyświetlania na ekranie, a nie do druku, przeskalowanie w dół przed osadzeniem to właściwy kompromis, a ogólne dźwignie rozmiaru są opisane w optymalizacji rozmiaru pliku PDF

Na koniec świadomie zdecyduj o zasadzie koloru. Zachowanie profilu źródłowego jest właściwe dla pracy archiwalnej i drukarskiej; konwersja do przestrzeni obowiązującej dla całego dokumentu jest właściwa, gdy zestaw mieszanych fotografii musi wyglądać spójnie, a ścieżka konwersji jest opisana w przekolorowywaniu dokumentu do innej przestrzeni koloru. Jeśli musisz potwierdzić, co faktycznie trafiło do pliku, ścieżka inspekcji w ekstrakcji tekstu, obrazów i fontów zgłasza obiekty obrazów, jakie niesie dokument

Wejście nowoczesnych obrazów, zarządzanie kolorem i optymalizacja obrazów są częścią tej samej biblioteki dla Delphi, C++Builder i Free Pascal; pełna lista funkcji znajduje się na stronie PDF Library for Delphi