PDFは画像をコンテンツストリーム内のファーストクラスオブジェクトとして保存します。ページが写真、スキャン、または図を参照する場合、ピクセルデータはページのジオメトリとともにXObject辞書内に存在します。PDFiumコンポーネントは、TPdfの2つのプロパティを通じてそれを表面化します:現在のページにある埋め込みビットマップの数を返すBitmapCountと、そのうちの1つをデコードして所有する(そして解放しなければならない)TBitmapにするBitmap[Index]です。これが抽出モデルのすべてです。ループは4行ですが、判断が必要なのはその周囲の配管です
ドキュメントを開く
TPdfについて最初に知っておくべきことは、Active := Trueが例外を発生させないということです。読み込みの失敗、間違ったパスワード、破損したファイル:これらはすべて内部で飲み込まれ、コンポーネントは単に非アクティブのままになります。割り当て後に自分でフラグを確認しなければ、PageCountがゼロを返す状態でページループに進み、何も抽出されない理由を不思議に思うことになります
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;
パスワードで保護されたファイルも同じパターンに従います:Active := Trueを設定する前にPdf.Passwordを割り当てます。パスワードが間違っている場合、ActiveはFalseのままで、キャッチする例外は発生しません。何百ものファイルを処理するバッチツールでは、このサイレントな動作は実際には便利です:それぞれのコールスタックを巻き戻すのではなく、失敗をリストに蓄積することができます
ページを反復処理してビットマップを取得する
BitmapCountはページごとなので、それを読み取る前にPdf.PageNumberを設定します。ページ番号は1ベースです。デフォルトは0で、これはページが読み込まれていないことを意味します。Bitmap[Index]プロパティは0ベースで、呼び出し元が所有するTBitmapを返します。これを解放する必要があります。大きなドキュメントでの長いループ内で解放を怠ると、各ビットマップは圧縮前の生ピクセルデータとして数メガバイトになる可能性があるため、メモリが急速に増加します
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;
Assignedによるガードは重要です。少数のPDFジェネレーターは、ピクセル次元がゼロ、またはその他の不正なデータを持つ画像XObjectを書き込みます。そのような場合、コンポーネントは空のビットマップではなくnilを返します。nilの戻り値をエラーとして扱い抽出を停止するのは誤った反射です:それをスキップし、監査証跡が必要な場合はページとインデックスをログに記録して、続行してください。ページの残りの部分は依然として有効な画像を生成する可能性があります
外側のループが反復ごとにPdf.PageNumberを設定していることに注意してください。この割り当てによってページがコンポーネントの内部状態に読み込まれ、BitmapCountが意味を持つようになります。これをスキップすると、同じページのカウントを繰り返し読み取ることになります。記述するときはこのパターンは冗長に感じられますが、これはAPIの設計によるものです:ページはカーソルであり、コレクションではありません
出力フォーマットの選択
BMPはロスレスであり、追加のユニットなしで常に利用できるため、画像に何が含まれているかまだわからない場合の堅実なデフォルトになります。ファイルサイズが重要な場合、返されたTBitmapのピクセルフォーマットが適切なコーデックを示します。32ビットビットマップはアルファチャンネルを持ちます。PNGはそれを損失なく保持します。連続階調を持つ大きな24ビット画像はJPEGの候補です。小さな画像や限られたパレットで描画された画像は、低品質設定ではブロックノイズを追加し、高品質設定ではほとんど節約にならないJPEGを通すよりも、一般的にBMPのままにしておく方が良いでしょう
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;
実際には、フォーマットの選択はBmp.PixelFormatと次元によって決まります。PixelFormat = pf32bitの場合、アルファを保持するフォーマットが必要です。PNGは明らかな選択肢ですが、古いDelphiバージョンではPNGImageユニットが必要です。幅がおよそ300ピクセルを超える24ビット画像の場合、品質85のJPEGは、ほとんどの写真コンテンツにおいて知覚できるほどの劣化なしにBMPに対して3対1のサイズ縮小を提供します。その閾値以下では、BMPはサイズ的に遜色なく、品質の判断を完全に避けることができます
BitmapCountがカウントするものとしないもの
PDFは、画像XObjectとパスオペレーターで描画されたベクターグラフィックスを区別します。視覚的に複雑に見えるページでも、すべての要素がベクターである場合、BitmapCountはゼロを返すことがあります。スキャンされたページはほぼ常に正確に1を返します:スキャナーは、設定された解像度でスキャン全体を単一のフルページ画像XObjectとして書き込みます。タイプセットされたテキストと埋め込まれた写真を混在させたページは、写真ごとに1つのエントリを返します。装飾用の罫線、影付きの背景、テーブルの境界線は、通常、ビットマップのカウントにはまったく表示されません
このカウントにはインライン画像も含まれません。これは、画像データが名前付きXObjectとしてではなく、ページのコンテンツストリームに直接埋め込まれる、めったに使用されないPDF構造です。これらはこのAPIが表面化する範囲外です。実際のドキュメントでは非常に珍しいため、ほとんどの抽出ツールは単にこれらを処理しません
覚えておく価値のある詳細の1つ:読み取るBitmapCountは、最後のPdf.PageNumberの割り当て時点での現在のページに対するものです。カウントとフェッチの間にコードが分岐したり、PageNumberを変更する関数を呼び出したりすると、スペースを割り当てた数よりも少ない画像を読み取ったり、インデックスが最後を超えたりする可能性があります。その間にPageNumberに触れることなく、カウントの読み取りとBitmap[]ループを同じページに保ってください
フォームアプリケーションでのTPdfViewの使用
TPdfViewコンポーネントは同じBitmapCountプロパティとBitmap[]プロパティを公開しますが、それが読み取るページはTPdf.PageNumberではなく、ビューで現在表示されているページです。2つのページポインターは独立しています。一方を設定しても他方は移動しません。ライブビューアーを備えたVCLフォームアプリケーションでは、Pdf.PageNumber := Nを呼び出してTPdfを介して抽出を推進しながら、ビューアーをユーザーが最後にスクロールした場所にとどめておくことができます。この分離は意図的なものであり、バックグラウンドでの抽出が実行されている間、ビューアーの表示状態をクリーンに保ちます
バッチジョブにおけるメモリとパフォーマンス
大規模なアーカイブ全体で、主に注意すべきはメモリの予算です。各Bitmap[]呼び出しはヒープ上に新しいTBitmapを割り当てます。300 DPIのスキャンされたページでは、エンコード前の生ピクセルデータで簡単に25 MBになります。反復の間に解放せずにタイトなループでページを処理すると、ワーキングセットは画像の数に比例して直線的に増加します。正しい形は常に:1つのビットマップを取得し、必要な処理を行い、それを解放し、次を取得することです。比較ステップのために一度に複数のビットマップへの参照を保持する必要がある場合は、まずBitmapCountでそれらを数え、それに応じてコンテナーを割り当てます。そして、ドキュメントの最後でのクリーンアップに延期するのではなく、完了したらすぐにそれぞれを解放します。500のスキャンされたページを持つドキュメントでは、この違いが25 MBと12 GBのピークRSSの違いを意味する可能性があります
ここで示されているBitmapCountおよびBitmap[]プロパティは、DelphiおよびC++Builder用のPDFiumコンポーネントの一部です