Artikel Teknis

Mengekstrak Gambar dari Dokumen PDF dengan PDFium Component di Delphi

PDF menyimpan gambar sebagai objek kelas satu di dalam aliran kontennya. Ketika halaman merujuk ke foto, hasil pemindaian, atau diagram, data piksel berada dalam kamus XObject bersama geometri halaman. PDFium Component mengeksposnya melalui dua properti pada TPdf: BitmapCount, yang mengembalikan jumlah bitmap tertanam pada halaman saat ini, dan Bitmap[Index], yang mendekode salah satunya menjadi TBitmap yang menjadi milik Anda dan harus dibebaskan. Itulah seluruh model ekstraksinya. Perulangannya hanya empat baris; yang memerlukan pertimbangan adalah infrastruktur di sekelilingnya

Membuka dokumen

Hal pertama yang perlu diketahui tentang TPdf ialah bahwa Active := True tidak pernah memunculkan pengecualian. Kegagalan memuat, kata sandi yang salah, atau file yang rusak semuanya ditangani secara internal dan komponen hanya tetap tidak aktif. Anda harus memeriksa sendiri statusnya setelah penugasan, atau Anda akan memasuki perulangan halaman dengan PageCount yang mengembalikan nol lalu bertanya-tanya mengapa tidak ada yang diekstrak

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');
    // lanjutkan ke ekstraksi
  finally
    Pdf.Free;
  end;
end;

File yang dilindungi kata sandi mengikuti pola yang sama: tetapkan Pdf.Password sebelum mengatur Active := True. Jika kata sandinya salah, Active tetap False dan tidak ada pengecualian untuk ditangkap. Dalam alat batch yang memproses ratusan file, perilaku diam ini justru berguna: Anda mengumpulkan kegagalan dalam sebuah daftar alih-alih membongkar tumpukan pemanggilan untuk setiap file

Mengiterasi halaman dan mengambil bitmap

BitmapCount berlaku per halaman, sehingga Anda perlu mengatur Pdf.PageNumber sebelum membacanya. Nomor halaman berbasis 1; nilai bawaan 0 berarti tidak ada halaman yang dimuat. Properti Bitmap[Index] berbasis 0 dan mengembalikan TBitmap yang menjadi milik pemanggil. Anda harus membebaskannya. Jika pembebasan diabaikan dalam perulangan panjang untuk dokumen besar, penggunaan memori akan cepat naik karena setiap bitmap dapat memuat beberapa megabita data piksel mentah sebelum kompresi

Loop ekstraksi gambar PDF di Delphi dengan PDFium, memeriksa flag Active yang senyap, mengatur PageNumber per halaman, membaca BitmapCount, melewati bitmap nil, dan membebaskan setiap TBitmap setelah menyimpan
Loop hanya membaca hitungan setelah kursor halaman bergerak, melewatkan bitmap nil dari XObject cacat, dan membebaskan setiap TBitmap milik pemanggil sebelum mengambil berikutnya
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;

Pemeriksaan Assigned penting. Sejumlah kecil pembuat PDF menulis XObject gambar dengan dimensi piksel nol atau data yang rusak dengan cara lain; dalam kasus tersebut komponen mengembalikan nil, bukan bitmap kosong. Menganggap hasil nil sebagai galat lalu menghentikan ekstraksi bukanlah tindakan yang tepat: lewati gambar itu, catat halaman dan indeksnya bila Anda memerlukan jejak audit, lalu lanjutkan. Bagian lain dari halaman tersebut mungkin masih menghasilkan gambar yang valid

Perhatikan bahwa perulangan luar menetapkan Pdf.PageNumber pada setiap iterasi. Penugasan itulah yang memuat halaman ke status internal komponen dan membuat BitmapCount bermakna. Jika dilewati, Anda akan membaca jumlah gambar halaman yang sama berulang kali. Pola ini mungkin terasa berlebihan saat ditulis, tetapi demikianlah rancangan API: halaman adalah kursor, bukan koleksi

Memilih format keluaran

BMP bersifat lossless dan selalu tersedia tanpa unit tambahan, sehingga menjadi pilihan bawaan yang baik ketika Anda belum mengetahui isi gambar. Ketika ukuran file penting, format piksel dari TBitmap yang dikembalikan menunjukkan codec yang sesuai. Bitmap 32-bit membawa kanal alfa; PNG mempertahankannya tanpa kehilangan kualitas. Gambar 24-bit besar dengan rona yang berkelanjutan cocok untuk JPEG. Gambar yang lebih kecil atau yang dibuat dengan palet terbatas umumnya lebih baik dibiarkan sebagai BMP daripada diproses melalui JPEG, yang menambahkan artefak blok pada pengaturan kualitas rendah dan hanya sedikit menghemat ukuran pada pengaturan tinggi

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;

Dalam praktiknya, pemilihan format ditentukan oleh Bmp.PixelFormat dan dimensi. Jika PixelFormat = pf32bit, Anda membutuhkan format yang membawa alfa; PNG adalah pilihan yang jelas, meski memerlukan unit PNGImage pada versi Delphi yang lebih lama. Untuk gambar 24-bit yang lebarnya lebih dari sekitar 300 piksel, JPEG pada kualitas 85 memberikan pengurangan ukuran tiga banding satu dibandingkan BMP tanpa kehilangan yang terlihat pada sebagian besar konten fotografis. Di bawah ambang itu, ukuran BMP sebanding dan tidak memerlukan keputusan kualitas apa pun

Apa yang dihitung dan tidak dihitung oleh BitmapCount

PDF membedakan XObject gambar dari grafik vektor yang digambar dengan operator jalur. Halaman yang tampak kompleks secara visual dapat mengembalikan BitmapCount bernilai nol apabila setiap elemennya berupa vektor. Halaman hasil pemindaian hampir selalu mengembalikan tepat satu: pemindai menulis seluruh hasil pindai sebagai satu XObject gambar satu halaman penuh pada resolusi yang dipilih pemindai. Halaman yang memadukan teks tersusun huruf dengan foto tertanam mengembalikan satu entri untuk setiap foto. Garis dekoratif, latar berarsir, dan batas tabel biasanya sama sekali tidak muncul dalam jumlah bitmap

Apa yang dicakup BitmapCount pada halaman PDF di PDFium, menghitung image XObject seperti hasil pemindaian dan foto sementara teks vektor, garis penggaris, isian berbayang, dan gambar inline tetap di luar hitungan
Halaman hasil pindai mengembalikan tepat satu image XObject sehalaman penuh, sementara halaman murni vektor bisa tampak ramai namun mengembalikan hitungan nol

Jumlah ini juga tidak mencakup gambar sebaris, sebuah konstruksi PDF yang jarang digunakan ketika data gambar disematkan langsung dalam aliran konten halaman alih-alih sebagai XObject bernama. Gambar tersebut berada di luar cakupan API ini; dalam dokumen nyata, penggunaannya cukup jarang sehingga sebagian besar alat ekstraksi memang tidak menanganinya

Satu detail yang perlu diingat: BitmapCount yang dibaca berlaku untuk halaman saat ini berdasarkan penugasan PageNumber terakhir. Jika kode Anda bercabang atau memanggil fungsi yang mengubah PageNumber antara penghitungan dan pengambilan, Anda dapat membaca gambar lebih sedikit daripada ruang yang telah dialokasikan, atau mengakses indeks melewati batas. Pertahankan pembacaan jumlah dan perulangan Bitmap[] pada halaman yang sama tanpa menyentuh PageNumber di antaranya

Menggunakan TPdfView dalam aplikasi formulir

Komponen TPdfView mengekspos properti BitmapCount dan Bitmap[] yang sama, tetapi halaman yang dibacanya adalah halaman yang sedang ditampilkan oleh tampilan, bukan TPdf.PageNumber. Kedua penunjuk halaman tersebut berdiri sendiri; mengatur satu tidak memindahkan yang lain. Dalam aplikasi formulir VCL dengan penampil langsung, Anda dapat memanggil Pdf.PageNumber := N untuk menjalankan ekstraksi melalui TPdf sementara penampil tetap berada pada halaman terakhir yang digulir pengguna. Pemisahan ini disengaja dan menjaga status tampilan penampil tetap bersih saat ekstraksi latar belakang berjalan

Memori dan performa dalam pekerjaan batch

Dalam arsip besar, anggaran memori adalah hal utama yang perlu diperhatikan. Setiap pemanggilan Bitmap[] mengalokasikan TBitmap baru di heap, dan pada halaman hasil pindai 300 DPI ukurannya dapat dengan mudah mencapai 25 MB data piksel mentah sebelum penyandian. Jika halaman diproses dalam perulangan ketat tanpa pembebasan antariterasi, kumpulan kerja bertumbuh secara linear dengan jumlah gambar. Pola yang benar selalu: ambil satu bitmap, lakukan yang diperlukan, bebaskan, lalu ambil bitmap berikutnya. Jika Anda perlu menyimpan referensi ke beberapa bitmap sekaligus untuk langkah perbandingan, hitung dahulu dengan BitmapCount dan alokasikan kontainer Anda sesuai kebutuhan, kemudian bebaskan masing-masing segera setelah selesai alih-alih menundanya hingga pembersihan di akhir dokumen. Pada dokumen dengan 500 halaman hasil pindai, perbedaan itu dapat berarti puncak RSS 25 MB alih-alih 12 GB

Properti BitmapCount dan Bitmap[] yang ditampilkan di sini merupakan bagian dari PDFium Component untuk Delphi dan C++Builder