PDF speichert Bilder als First-Class-Objekte innerhalb seiner Inhaltsströme. Wenn eine Seite auf ein Foto, einen Scan oder ein Diagramm verweist, leben die Pixeldaten in einem XObject-Wörterbuch neben der Seitengeometrie. Die PDFium-Komponente macht dies durch zwei Eigenschaften von TPdf zugänglich: BitmapCount, was zurückgibt, wie viele eingebettete Bitmaps sich auf der aktuellen Seite befinden, und Bitmap[Index], was eines davon in ein TBitmap dekodiert, das Ihnen gehört und das Sie freigeben müssen. Das ist das gesamte Extraktionsmodell. Die Schleife besteht aus vier Zeilen; was Urteilsvermögen erfordert, ist die umgebende Infrastruktur
Öffnen des Dokuments
Das Erste, was man über TPdf wissen muss, ist, dass Active := True niemals eine Ausnahme auslöst. Ladefehler, falsche Passwörter, beschädigte Dateien: all das wird intern abgefangen und die Komponente bleibt einfach inaktiv. Sie müssen das Flag nach der Zuweisung selbst überprüfen, sonst gehen Sie in die Seitenschleife, während PageCount null zurückgibt, und wundern sich, warum nichts extrahiert wurde
var
Pdf: TPdf;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := 'report.pdf';
Pdf.Active := True;
if not Pdf.Active then
begin
Writeln('Failed to open: ', Pdf.FileName);
Exit;
end;
Writeln(Pdf.PageCount, ' pages');
// proceed to extraction
finally
Pdf.Free;
end;
end;
Passwortgeschützte Dateien folgen demselben Muster: Weisen Sie Pdf.Password zu, bevor Sie Active := True setzen. Wenn das Passwort falsch ist, bleibt Active False und Sie erhalten keine Ausnahme, die Sie abfangen könnten. In einem Stapelverarbeitungstool, das Hunderte von Dateien verarbeitet, ist dieses stille Verhalten tatsächlich nützlich: Sie sammeln die Fehlschläge in einer Liste, anstatt den Aufrufstapel für jeden einzelnen abzuwickeln
Seiten iterieren und Bitmaps abrufen
BitmapCount ist pro Seite, also setzen Sie Pdf.PageNumber, bevor Sie es lesen. Seitenzahlen sind 1-basiert; der Standardwert ist 0, was bedeutet, dass keine Seite geladen ist. Die Eigenschaft Bitmap[Index] ist 0-basiert und gibt ein dem Aufrufer gehörendes TBitmap zurück. Sie müssen es freigeben. Vernachlässigen Sie die Freigabe innerhalb einer langen Schleife über ein großes Dokument und der Speicher steigt schnell an, da jede Bitmap vor jeder Komprimierung mehrere Megabyte an rohen Pixeldaten umfassen kann
procedure ExtractAllImages(Pdf: TPdf; const OutputDir: string);
var
Page, Idx: Integer;
Bmp: TBitmap;
OutPath: string;
begin
for Page := 1 to Pdf.PageCount do
begin
Pdf.PageNumber := Page;
for Idx := 0 to Pdf.BitmapCount - 1 do
begin
Bmp := Pdf.Bitmap[Idx];
if not Assigned(Bmp) then
Continue;
try
OutPath := Format('%s\p%d_img%d.bmp', [OutputDir, Page, Idx + 1]);
Bmp.SaveToFile(OutPath);
finally
Bmp.Free;
end;
end;
end;
end;
Die Assigned-Prüfung ist wichtig. Eine kleine Anzahl von PDF-Generatoren schreibt Bild-XObjects mit Null-Pixel-Dimensionen oder anderweitig fehlerhaften Daten; in diesen Fällen gibt die Komponente nil anstelle einer leeren Bitmap zurück. Einen nil-Rückgabewert als Fehler zu behandeln und die Extraktion zu stoppen, ist der falsche Reflex: Überspringen Sie es, protokollieren Sie die Seite und den Index, falls Sie den Prüfpfad benötigen, und fahren Sie fort. Der Rest der Seite kann immer noch gültige Bilder liefern
Beachten Sie, dass die äußere Schleife Pdf.PageNumber bei jeder Iteration setzt. Diese Zuweisung ist es, die die Seite in den internen Status der Komponente lädt und BitmapCount aussagekräftig macht. Überspringen Sie dies, lesen Sie wiederholt die Zählung derselben Seite. Das Muster fühlt sich beim Schreiben redundant an, aber so ist die API entworfen: Die Seite ist ein Cursor, keine Auflistung
Wahl eines Ausgabeformats
BMP ist verlustfrei und immer ohne zusätzliche Units verfügbar, was es zu einem soliden Standard macht, wenn Sie noch nicht wissen, was das Bild enthält. Wenn die Dateigröße wichtig ist, sagt Ihnen das Pixelformat des zurückgegebenen TBitmap, welcher Codec angemessen ist. Eine 32-Bit-Bitmap trägt einen Alphakanal; PNG bewahrt diesen ohne Verlust. Ein großes 24-Bit-Bild mit kontinuierlichen Tönen ist ein Kandidat für JPEG. Kleinere Bilder oder solche, die mit einer begrenzten Palette gezeichnet wurden, belässt man im Allgemeinen besser als BMP, anstatt sie durch JPEG laufen zu lassen, was bei niedrigen Qualitätseinstellungen Blockartefakte hinzufügt und bei hohen Einstellungen wenig spart
procedure SaveBitmap(Bmp: TBitmap; const FileName: string);
var
Jpg: TJPEGImage;
begin
case UpperCase(ExtractFileExt(FileName)) of
'.JPG', '.JPEG':
begin
Jpg := TJPEGImage.Create;
try
Jpg.Assign(Bmp);
Jpg.CompressionQuality := 85;
Jpg.SaveToFile(FileName);
finally
Jpg.Free;
end;
end;
else
Bmp.SaveToFile(FileName); // BMP: lossless, no extra units
end;
end;
In der Praxis wird die Formatwahl von Bmp.PixelFormat und den Dimensionen bestimmt. Wenn PixelFormat = pf32bit ist, benötigen Sie ein Format, das Alpha trägt; PNG ist die offensichtliche Wahl, obwohl es in älteren Delphi-Versionen die PNGImage-Unit erfordert. Für 24-Bit-Bilder, die breiter als etwa 300 Pixel sind, bietet JPEG bei Qualität 85 eine Reduzierung der Größe auf ein Drittel gegenüber BMP ohne wahrnehmbaren Verlust bei den meisten fotografischen Inhalten. Unterhalb dieser Schwelle ist BMP in der Größe vergleichbar und vermeidet jegliche Qualitätsentscheidung gänzlich
Was BitmapCount zählt und was nicht
PDF unterscheidet zwischen Bild-XObjects und Vektorgrafiken, die mit Pfadoperatoren gezeichnet wurden. Eine Seite, die visuell komplex aussieht, kann einen BitmapCount von null zurückgeben, wenn jedes Element ein Vektor ist. Eingescannte Seiten geben fast immer genau eins zurück: Der Scanner schreibt den gesamten Scan als ein einziges ganzseitiges Bild-XObject mit der Auflösung, auf die der Scanner eingestellt war. Seiten, die gesetzten Text mit eingebetteten Fotos mischen, geben einen Eintrag pro Foto zurück. Dekorative Linien, schattierte Hintergründe und Tabellenränder erscheinen in der Bitmap-Zählung normalerweise überhaupt nicht
Die Zählung schließt auch keine Inline-Bilder ein, ein selten verwendetes PDF-Konstrukt, bei dem Bilddaten direkt im Inhaltsstrom der Seite eingebettet sind, anstatt als benanntes XObject. Diese fallen außerhalb dessen, was diese API zugänglich macht; sie sind in realen Dokumenten selten genug, dass die meisten Extraktionstools sie einfach nicht handhaben
Ein Detail, das es wert ist, beachtet zu werden: Der BitmapCount, den Sie lesen, gilt für die aktuelle Seite ab der letzten Zuweisung von PageNumber. Wenn Ihr Code verzweigt oder eine Funktion aufruft, die PageNumber zwischen dem Zählen und dem Abrufen ändert, lesen Sie möglicherweise weniger Bilder, als Sie Platz dafür zugewiesen haben, oder indexieren über das Ende hinaus. Halten Sie das Lesen der Zählung und die Bitmap[]-Schleife auf derselben Seite, ohne dazwischen PageNumber anzufassen
Verwendung von TPdfView in einer Formularanwendung
Die TPdfView-Komponente legt dieselben Eigenschaften BitmapCount und Bitmap[] offen, aber die Seite, von der sie liest, ist die aktuell angezeigte Seite der Ansicht, nicht TPdf.PageNumber. Die beiden Seitenzeiger sind unabhängig; das Setzen des einen bewegt den anderen nicht. In einer VCL-Formularanwendung mit einem Live-Viewer können Sie Pdf.PageNumber := N aufrufen, um die Extraktion durch TPdf zu steuern, während der Viewer auf dem bleibt, wohin der Benutzer zuletzt gescrollt hat. Diese Trennung ist beabsichtigt und hält den Anzeigezustand des Viewers sauber, während eine Hintergrundextraktion läuft
Speicher und Leistung bei Stapelverarbeitungsaufträgen
Über ein großes Archiv hinweg ist das Speicherbudget die Hauptsache, auf die man achten muss. Jeder Bitmap[]-Aufruf weist ein neues TBitmap auf dem Heap zu, und auf einer eingescannten Seite mit 300 DPI sind das leicht 25 MB an rohen Pixeldaten vor jeder Codierung. Wenn Sie Seiten in einer engen Schleife verarbeiten, ohne zwischen den Iterationen freizugeben, wächst das Working Set linear mit der Anzahl der Bilder. Die richtige Form ist immer: eine Bitmap abrufen, tun, was Sie tun müssen, sie freigeben, die nächste abrufen. Wenn Sie Referenzen auf mehrere Bitmaps gleichzeitig für einen Vergleichsschritt halten müssen, zählen Sie diese zuerst mit BitmapCount und weisen Sie Ihren Container entsprechend zu, dann geben Sie jede einzelne frei, sobald Sie mit ihr fertig sind, anstatt dies auf die Bereinigung am Ende des Dokuments zu verschieben. Bei einem Dokument mit 500 eingescannten Seiten kann dieser Unterschied den Unterschied zwischen 25 MB und 12 GB Spitzen-RSS ausmachen
Die hier gezeigten Eigenschaften BitmapCount und Bitmap[] sind Teil der PDFium-Komponente für Delphi und C++Builder