Merender halaman PDF ke JPEG terdiri dari dua operasi yang cenderung dijalankan secara bersamaan namun didebug secara terpisah. Pertama, Anda merasterisasi halaman menjadi bitmap piksel pada resolusi yang Anda pilih. Kemudian, Anda menyerahkan bitmap tersebut ke enkodir JPEG dan memilih kualitasnya. PDFium Component menangani bagian pertama melalui fungsi RenderPage; bagian kedua adalah VCL murni, yaitu TJPEGImage dari Vcl.Imaging.jpeg. Hubungan di antara keduanya adalah tempat di mana keputusan penting berada, karena resolusi yang Anda pilih pada sisi rendering dan kualitas yang Anda pilih pada sisi encoding saling mempengaruhi satu sama lain dan juga ukuran file dengan cara yang mudah salah dihitung
Hal yang perlu diinternalisasi sebelum menulis kode apa pun: halaman PDF tidak memiliki piksel. Halaman PDF dideskripsikan dalam poin, di mana satu poin setara dengan 1/72 inci, dan halaman tersebut adalah gambar vektor yang diukur dalam poin tersebut. Ketika Anda meminta PDFium untuk merender, Anda memilih berapa banyak piksel untuk memproyeksikan gambar tersebut, dan pilihan itu adalah DPI. Jika aritmatikanya salah, Anda mungkin merender thumbnail yang buram padahal yang diinginkan adalah master cetak, atau Anda mengalokasikan bitmap 200 megapiksel untuk sesuatu yang ditujukan sebagai pratinjau 120 piksel
Dari DPI ke dimensi piksel
RenderPage memerlukan parameter integer piksel Width dan Height, bukan DPI. Jadi tugas pertama adalah mengonversinya. Sebuah halaman melaporkan ukurannya dalam poin melalui PageWidth dan PageHeight (keduanya bertipe Double), dan konversinya sama dengan yang digunakan oleh rasterizer mana pun: piksel sama dengan poin dikalikan target DPI dibagi 72. Halaman US Letter berukuran 612 x 792 poin. Pada 150 DPI itu menjadi 1275 x 1650 piksel; pada 72 DPI ukurannya tetap 612 x 792, satu piksel per poin, yang merupakan kasus yang sering dilupakan orang bahwa itu hanyalah identitas
// Pdf.PageNumber harus menunjuk pada halaman yang diinginkan.
PixelW := Round(Pdf.PageWidth * Dpi / 72);
PixelH := Round(Pdf.PageHeight * Dpi / 72);
Bitmap := Pdf.RenderPage(0, 0, PixelW, PixelH, ro0, [], clWhite);
// ... gunakan Bitmap ...
Bitmap.Free; // bentuk fungsi RenderPage menyerahkan kepemilikan kepada Anda
Dua detail dalam empat baris tersebut menentukan apakah kodenya benar. Pertama adalah bahwa bentuk fungsi dari RenderPage mengembalikan TBitmap yang Anda miliki. PDFium mengalokasikannya dan melepaskannya; jika Anda tidak membebaskannya (Free) pada setiap iterasi, pemrosesan batch atas beberapa ratus halaman akan membocorkan bitmap dan memori proses akan membengkak hingga terjadi error. Detail kedua adalah argumen Color, di sini menggunakan clWhite. Halaman PDF biasanya digambar dengan asumsi latar belakang putih buram (opaque), dan halaman dengan transparansi yang dirender pada warna latar belakang yang salah akan menghasilkan tepi yang kotor atau lingkaran gelap yang mengganggu. Putih adalah default yang tepat untuk hampir semua dokumen; parameter ini ada untuk kasus langka di mana putih tidak diinginkan
Nilai 0, 0 adalah offset Left dan Top ke dalam halaman, dalam ruang koordinat yang diskalakan, dan biarkan tetap nol kecuali Anda sedang melakukan pemotongan (cropping). Nilai ro0 adalah rotasi: biarkan tetap nol dan PDFium akan mematuhi rotasi apa pun yang dideklarasikan halaman dalam entri /Rotate-nya, sehingga halaman yang ditulis dengan orientasi landscape akan keluar sebagai landscape tanpa Anda perlu melakukan apa pun
Mengodekan bitmap sebagai JPEG
Setelah bitmap ada, JPEG adalah bagian yang mudah, dan itu adalah Delphi murni. TJPEGImage.Assign menyalin bitmap, CompressionQuality mengatur kualitas pada skala 1 hingga 100, dan SaveToFile menulis file. Satu-satunya aturan urutan adalah kualitas harus diatur sebelum Anda menyimpan, karena aturan tersebut mengatur proses encoding yang dipicu oleh SaveToFile
uses
Vcl.Graphics, Vcl.Imaging.jpeg, PDFium;
procedure SavePageAsJpeg(Pdf: TPdf; PageNumber, Dpi, Quality: Integer;
const FileName: string);
var
Bitmap: TBitmap;
Jpeg: TJPEGImage;
begin
Pdf.PageNumber := PageNumber;
Bitmap := Pdf.RenderPage(0, 0,
Round(Pdf.PageWidth * Dpi / 72),
Round(Pdf.PageHeight * Dpi / 72),
ro0, [], clWhite);
try
Jpeg := TJPEGImage.Create;
try
Jpeg.Assign(Bitmap);
Jpeg.CompressionQuality := Quality; // 1..100
Jpeg.SaveToFile(FileName);
finally
Jpeg.Free;
end;
finally
Bitmap.Free;
end;
end;
Blok try/finally bertingkat tersebut terlihat rumit untuk sebuah helper satu halaman, tetapi itu sangat tepat untuk pemrosesan batch. Blok dalam membebaskan encoder, blok luar membebaskan bitmap, dan jika salah satunya memicu eksepsi, sumber daya yang dimilikinya akan tetap dibebaskan. Jika Anda menggabungkannya menjadi satu, eksepsi saat encoding dapat menahan bitmap di memori. Dalam jangka panjang, ini adalah perbedaan antara konverter yang selesai dengan sukses dan yang mati di halaman 300 dengan file yang rusak dan dialog out-of-memory
Memilih DPI dan kualitas bersama-sama
Kedua opsi tersebut tidak independen dari tujuan output, dan kesalahan umum adalah menaikkan keduanya karena terlalu berhati-hati. Sebuah thumbnail web yang dirender pada 300 DPI dan disimpan pada kualitas 95 akan berukuran beberapa ratus kilobyte yang berpura-pura menjadi gambar 120 piksel; browser akan membuang hampir semuanya saat melakukan downscale. Sesuaikan resolusi dengan piksel yang benar-benar dibutuhkan oleh output, lalu pilih kualitas yang dapat bertahan dari kompresi lossy JPEG tanpa menghasilkan artefak visual yang terlihat
| Output | DPI | Kualitas JPEG |
|---|---|---|
| Thumbnail daftar | 72 | 60-70 |
| Pratinjau layar | 96-150 | 80-85 |
| Tampilan detail tinggi | 200-300 | 85-95 |
| Master cetak | 300-600 | 90-100 |
Kualitas JPEG memerlukan sedikit perhatian ekstra. Ini bukan dial linear. Lompatan dari 70 ke 85 memberikan peningkatan visual yang nyata dengan pertumbuhan ukuran file yang moderat; lompatan dari 95 ke 100 secara kasar menggandakan ukuran file untuk perbedaan yang hampir tidak dapat dilihat oleh siapa pun, karena kualitas 100 tetap tidak lossless, hanya saja tidak banyak membuang data. Untuk halaman yang padat teks, kompresi berbasis blok JPEG akan mengaburkan tepi tajam dari glif menjadi efek bayangan halus (ringing), itulah sebabnya kualitas di bawah 80 membuat teks yang dipindai tampak tidak tajam pada output yang seharusnya bersih. Jika halaman sebagian besar berisi teks dan Anda dapat mengubah format, PNG akan merender teks tersebut tanpa ringing; JPEG lebih cocok untuk konten foto dan campuran di mana kompresinya benar-benar lebih kecil
Thumbnail yang lebih cepat dan lebih kecil
Ketika targetnya adalah thumbnail, Anda dapat memberi tahu renderer untuk melakukan pekerjaan yang lebih sedikit. Parameter Options menerima sekumpulan flag TRenderOption, dan beberapa di antaranya menukar ketepatan dengan kecepatan seperti yang dibutuhkan oleh pratinjau kecil. reGrayscale membuang warna, yang merender lebih cepat dan menghasilkan bitmap yang lebih kecil untuk dikodekan. reNoSmoothImage dan reNoSmoothPath melewati proses anti-aliasing yang tidak terlihat pada skala thumbnail
function RenderThumbnail(Pdf: TPdf; PageNumber, MaxW, MaxH: Integer): TBitmap;
var
Scale: Double;
begin
Pdf.PageNumber := PageNumber;
// Sesuaikan halaman di dalam MaxW x MaxH dengan tetap menjaga rasio aspek.
Scale := Min(MaxW / Pdf.PageWidth, MaxH / Pdf.PageHeight);
Result := Pdf.RenderPage(0, 0,
Round(Pdf.PageWidth * Scale),
Round(Pdf.PageHeight * Scale),
ro0, [reGrayscale, reNoSmoothImage], clWhite);
end;
Kasus thumbnail juga menunjukkan cara yang lebih bersih untuk memikirkan ukuran. Alih-alih menggunakan DPI, hitung faktor skala tunggal yang menyesuaikan halaman di dalam kotak pembatas dan mempertahankan rasio aspek, seperti yang dilakukan oleh nilai Min dari kedua rasio tersebut. Halaman portrait dan halaman landscape keduanya akan muat di dalam kotak yang sama tanpa distorsi, dan Anda tidak perlu memikirkan DPI apa yang sesuai dengan 'muat dalam 200 x 280.' Satu peringatan dengan reGrayscale: ini mengonversi konten gambar raster ke abu-abu, tetapi warna vektor dan teks mempertahankan nilai warnanya di dalam engine, sehingga halaman yang sebagian besar berisi seni vektor mungkin kurang monokrom daripada yang disarankan oleh nama flag tersebut. Untuk hasil grayscale penuh yang sebenarnya, mengonversi bitmap yang dirender dengan GrayscalePdfBitmap adalah jalan yang andal
Melakukan batch pada seluruh dokumen
Menyatukannya untuk seluruh dokumen adalah putaran atas PageCount, dengan PageNumber dipindahkan satu halaman per satu. Halaman berbasis 1: halaman pertama adalah PageNumber := 1, dan perulangan berjalan sampai PageCount inklusif, bukan PageCount - 1. Hal lain yang harus dipatuhi oleh pemrosesan batch adalah kontrak silent-load. Mengatur Active := True tidak pernah memicu eksepsi pada file yang rusak atau kata sandi yang salah; itu hanya menyisakan properti Active bernilai False. Periksa sebelum Anda merender halaman mana pun, atau pemanggilan RenderPage pertama akan bekerja pada dokumen yang sebenarnya tidak pernah berhasil dibuka
procedure ExportAllPages(const PdfPath, OutDir: string; Dpi, Quality: Integer);
var
Pdf: TPdf;
I, Digits: Integer;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := PdfPath;
Pdf.Active := True;
if not Pdf.Active then
raise Exception.Create('Tidak dapat membuka ' + PdfPath);
Digits := Length(IntToStr(Pdf.PageCount)); // padding nol agar pengurutan file benar
for I := 1 to Pdf.PageCount do
SavePageAsJpeg(Pdf, I, Dpi, Quality,
Format('%s\page_%.*d.jpg', [OutDir, Digits, I]));
finally
Pdf.Active := False;
Pdf.Free;
end;
end;
Pengisian nol (zero-padding) melalui variabel Digits adalah hal kecil yang menghemat waktu Anda nanti. Menamai file dari page_1.jpg hingga page_10.jpg akan membuat alat apa pun yang mengurutkannya sebagai string menempatkan page_10 tepat setelah page_1, mengacaukan urutannya. Menambahkan padding sesuai dengan lebar nomor halaman tertinggi, sehingga dokumen 300 halaman menghasilkan page_001.jpg, menjaga urutan leksikal dan urutan halaman tetap identik di mana pun pada tahap berikutnya
Untuk dokumen yang cukup besar sehingga konversinya memakan waktu lama, jalankan di luar thread UI atau proses pesan (pump messages) di antara halaman agar aplikasi tetap responsif, dan berikan cara bagi pengguna untuk menghentikannya. Jika Anda merender halaman yang sangat besar dan menginginkan pembatalan (cancellation) yang bekerja di tengah halaman daripada hanya di antara halaman, PDFium Component memiliki jalur rendering progresif dengan cancellation token; itu adalah mekanisme yang lebih berat daripada yang dibutuhkan sebagian besar ekspor batch, tetapi mekanisme itu tersedia ketika satu halaman pada 600 DPI terasa cukup lambat untuk memblokir UI
Satu pasangan terakhir yang layak diketahui. Merasterisasi halaman akan membuang lapisan teksnya: file JPEG adalah piksel, dan kata-kata di dalamnya tidak lagi dapat dipilih atau dicari. Ketika Anda memerlukan gambar dan teks di bawahnya, render untuk gambar dan tarik teksnya secara terpisah, seperti yang dibahas dalam artikel pendamping tentang mengekstrak teks dari dokumen PDF dengan PDFium Component. Overload RenderPage dan opsi render yang ditunjukkan di sini adalah bagian dari PDFium Component untuk Delphi dan C++Builder