Technischer Artikel

AVIF-, HEIF- und JPEG-XL-Bilder in Delphi in PDF einbetten

PDF Library for Delphi akzeptiert AVIF-, HEIF- und JPEG-XL-Bilder als Eingabe über AddModernImageFromFile sowie dessen Stream- und String-Varianten und erhält dabei Alphakanal, das eingebettete ICC-Profil und 16-Bit-Kanäle auf dem Weg in das PDF-Bildobjekt. Die Formaterkennung erfolgt anhand eines begrenzten Magic-Number-Lesevorgangs, und das Dekodieren läuft über ein austauschbares Backend, sodass für eine Datei, die tatsächlich keinem dieser Formate entspricht, nichts Externes aufgerufen wird

Diese Formate fanden über Smartphones Einzug in Dokument-Workflows. iOS erzeugt seit Jahren standardmäßig HEIC, Android-Geräte erzeugen AVIF, und ein Außendiensttechniker, der ein beschädigtes Teil fotografiert, schickt ein Bild, das ein 2015 gebauter PDF-Berichtsgenerator überhaupt nicht öffnen kann. Der generische Fallback-Pfad, das Dekodieren über eine Plattform-Bitmap, liefert zuverlässig 8-Bit-Farbe und verliert dabei Alphakanal und Farbprofil

Was erhält der moderne Bildpfad, das eine Bitmap-Konvertierung verliert?

Drei Dinge, und jedes hat einen Workflow, der davon abhängt. Der Alphakanal bleibt erhalten, was für Logos und über Seiteninhalt gelegte Produktfreisteller wichtig ist. Das ICC-Profil bleibt erhalten, was für alles wichtig ist, das gedruckt oder farblich abgeglichen wird. Und 16-Bit-Kanäle bleiben erhalten, was für medizinische und wissenschaftliche Bildgebung wichtig ist, wo 8-Bit-Quantisierung genau die Abstufungen zerstört, für die das Bild aufgenommen wurde

Ein Bild durch eine Plattform-Bitmap zu schicken verliert alle drei in einem Schritt, und das geschieht lautlos: Das entstehende PDF sieht ungefähr richtig aus, und niemand bemerkt es, bis eine Druckerei fragt, warum das Firmenrot falsch ist. Optionswert 8 bei den modernen Bildaufrufen ist das Flag, das Alphakanal, ICC-Profil und 16-Bit-Kanäle zusammen erhält, und es ist der Standard für diese Aufrufe

Ein Bild einer Seite hinzufügen

Der Aufruf gibt eine Bildkennung zurück, die dann ausgewählt und gezeichnet oder in einem Schritt gezeichnet und freigegeben wird:

uses
  PDFlibrary, PDFlibModernImage;

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

    // Options = 8 erhält Alphakanal, ICC-Profil und 16-Bit-Kanäle
    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;

Die Erkennung erfolgt vor dem Dekodieren und ist bewusst eng gefasst. Die Bibliothek liest einen begrenzten Header, erkennt die ISO-Base-Media-File-Format-Kennungen, die AVIF und HEIF identifizieren, sowie sowohl die rohe als auch die Container-Signatur von JPEG XL, und stellt anschließend die Stream-Position des Aufrufers wieder her. Eine unbekannte oder getarnte Eingabe erreicht niemals den externen Codec, was verhindert, dass eine umbenannte ausführbare Datei einem Decoder übergeben wird, als wäre sie ein Bild

Wo findet das Dekodieren tatsächlich statt?

Moderne Bildformate sind große, komplexe Codecs, und einen davon in eine PDF-Bibliothek zu packen wäre eine seltsame Designentscheidung. Das Standard-Backend lädt dynamisch ein bereitstellbares MagickWand-Modul im selben Prozess und sucht danach in einer dokumentierten Reihenfolge: ein explizit gesetzter Datei- oder Verzeichnispfad, Umgebungsvariablen, das Verzeichnis der ausführbaren Datei und der System-Suchpfad

Anwendungen, die bereits einen Decoder mitbringen oder überhaupt kein externes Modul laden dürfen, registrieren stattdessen ihren eigenen Callback. Der Vertrag ist klein: den Eingabestream lesen, ein PNG in den Ausgabestream schreiben, die angeforderte Orientierung beachten:

function MyDecoder(InStream, OutPNG: TStream;
  ImageFormat: TPDFlibModernImageFormat;
  ApplyOrientation: Boolean): Boolean;
begin
  // InStream mit dem eigenen Codec dekodieren und PNG-Bytes in OutPNG schreiben
  Result := DecodeWithBundledCodec(InStream, OutPNG,
    ImageFormat, ApplyOrientation);
end;

begin
  RegisterModernImageDecoderBackend(MyDecoder);
  // ... Bilder hinzufügen ...
  ClearModernImageDecoderBackend;    // zurück zum Standard-Backend
end;

Die Bereitstellung bringt eine Annehmlichkeit und eine bewusste Zurückhaltung mit sich. Enthält das Codec-Verzeichnis ein Unterverzeichnis modules\coders, füllt die Bibliothek die Codec-Umgebungsvariablen aus, die ein solches Layout benötigt, jedoch nur, wenn die Host-Anwendung sie nicht bereits gesetzt hat. Eine Anwendung mit eigener Runtime-Bereitstellungsstrategie behält diese bei

Warum PNG als Zwischenschritt?

Die Überbrückung über ein PNG im Arbeitsspeicher statt über einen rohen Pixelpuffer wirkt wie ein zusätzlicher Schritt und ist tatsächlich der günstigste korrekte. PNG drückt alles aus, was erhalten bleiben muss – Alphakanal, Farbtyp, Bittiefe und ein eingebettetes ICC-Profil –, und die Bibliothek besitzt bereits einen ausgereiften, gut getesteten Pfad von PNG in ein PDF-Bildobjekt mit den richtigen Filtern und dem richtigen Farbraum. Ihn wiederzuverwenden bedeutet, dass moderne Formate von Jahren an Korrektheitsarbeit profitieren, statt eine parallele Implementierung zu bekommen

Die Brücke liegt vollständig im Arbeitsspeicher, daher werden keine temporären Dateien erstellt und es ist keine Aufräumarbeit bei einem Absturz nötig. Eine Besonderheit brauchte explizite Behandlung: Manche Konvertierungen verwerfen das ICC-Profil beim Formatwechsel. Das Backend erfasst daher das Quellprofil vor dem Formatwechsel, komprimiert es mit Flate, baut einen gültigen iCCP-Chunk mit neu berechneter CRC auf und entfernt einen etwaigen sRGB-Chunk, der damit in Konflikt stünde. Im Test behielt ein dekodiertes AVIF 16-Bit-RGBA mit 16-Bit-Alphakanal, und das aus dem entstandenen PDF extrahierte Profil stimmte mit dem Quellprofil byteidentisch bei 60.960 Bytes überein

Praktische Hinweise vor der Aktivierung im Produktivbetrieb

Verfügbarkeit beim Start prüfen, nicht erst beim ersten Foto. ModernImageCodecAvailable meldet, ob ein Backend genutzt werden kann, und SetModernImageCodecLibrary verweist auf eine explizite Datei oder ein Verzeichnis, wenn das eigene Deployment den Codec an ungewöhnlicher Stelle ablegt:

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

Die Dateigröße des Ergebnisses im Blick behalten. Ein 16-Bit-RGBA-Bild mit eingebettetem Profil ist ein großes PDF-Bildobjekt, und ein Bericht mit vierzig davon wird groß. Ist das Dokument für die Bildschirmanzeige statt für den Druck bestimmt, ist Downsampling vor dem Einbetten der richtige Kompromiss, und die allgemeinen Stellschrauben für die Größe werden in PDF-Dateigrößenoptimierung behandelt

Schließlich sollte die Farbpolitik bewusst festgelegt werden. Das Quellprofil zu behalten ist für Archivierung und Druckarbeit korrekt; die Konvertierung in einen dokumentweiten Farbraum ist korrekt, wenn eine gemischte Menge von Fotografien einheitlich aussehen muss, und der Konvertierungsweg wird in Umfärben eines Dokuments in einen anderen Farbraum beschrieben. Muss bestätigt werden, was tatsächlich in der Datei gelandet ist, meldet der Inspektionspfad in Text-, Bild- und Schriftextraktion die Bildobjekte, die ein Dokument trägt

Moderne Bildeingabe, Farbmanagement und Bildoptimierung sind Teil derselben Bibliothek für Delphi, C++Builder und Free Pascal; die vollständige Funktionsliste findet sich auf der PDF-Library-für-Delphi-Seite