Izrisovanje strani PDF v sliko JPEG je sestavljeno iz dveh operacij, ki ju programerji pogosto združijo in nato ločeno odpravljajo napake. Najprej stran rasterizirate v bitno sliko (bitmap) pri izbrani ločljivosti. Nato to bitno sliko posredujete kodirniku JPEG in izberete kakovost. PDFium VCL opravi prvi del prek metode RenderPage; drugi del pa je običajen VCL, natančneje razred TJPEGImage iz enote Vcl.Imaging.jpeg. Spoj med njima je mesto, kjer se sprejemajo zanimive odločitve, saj so ločljivost na strani izrisovanja, kakovost na strani kodiranja in končna velikost datoteke v medsebojnem soodvisju, ki ga je enostavno napačno oceniti.
Pred pisanjem kakršne koli kode si morate zapomniti naslednje: stran PDF nima slikovnih pik (pixels). Opisana je v točkah (points), kjer ena točka predstavlja 1/72 palca, stran pa je vektorska risba, merjena v teh točkah. Ko od PDFium-a zahtevate izris, izbirate, na koliko slikovnih pik boste projicirali to risbo, ta izbira pa je vrednost DPI (pik na palec). Če se zmotite pri izračunu, boste bodisi dobili zamegljeno sličico namesto tiskarske predloge bodisi alocirali bitno sliko z 200 megapiksli za nekaj, kar je namenjeno le predogledu velikosti 120 slikovnih pik.
Iz DPI v dimenzije slikovnih pik
Metoda RenderPage pričakuje celoštevilčni vrednosti za širino (Width) in višino (Height) v slikovnih pikah in ne vrednosti DPI. Prva naloga je torej pretvorba. Stran sporoča svojo velikost v točkah prek lastnosti PageWidth in PageHeight (obe sta tipa Double), pretvorba pa je enaka tisti, ki jo uporablja vsak rasterizator: slikovne pike so enake točkam, pomnoženim s ciljnim DPI in deljenim z 72. Stran formata US Letter meri 612 × 792 točk. Pri 150 DPI to postane 1275 × 1650 slikovnih pik; pri 72 DPI pa ostane 612 × 792, kar pomeni eno slikovno piko na točko.
// Pdf.PageNumber must already point at the page you want.
PixelW := Round(Pdf.PageWidth * Dpi / 72);
PixelH := Round(Pdf.PageHeight * Dpi / 72);
Bitmap := Pdf.RenderPage(0, 0, PixelW, PixelH, ro0, [], clWhite);
// ... use Bitmap ...
Bitmap.Free; // the function-form RenderPage hands you ownership
Dve podrobnosti v teh štirih vrsticah določata, ali je koda pravilna. Prva je ta, da funkcijska oblika metode RenderPage vrne objekt TBitmap, katerega lastnik ste vi. PDFium ga je alociral in prepustil vam; če ga ne sprostite z metodo Free v vsakem koraku zanke, bo paketna obdelava nekaj sto strani povzročila uhajanje (leak) nekaj sto bitnih slik, proces pa bo naraščal, dokler se ne sesuje. Druga podrobnost je argument Color, ki je tukaj nastavljen na clWhite. Strani PDF so običajno izrisane ob predpostavki neprozornega belega ozadja, izris strani s prosojnostjo na napačno barvo ozadja pa povzroči nejasne robove ali neželene temne sije. Bela barva je pravilna privzeta nastavitev za skoraj vsak dokument, parameter pa obstaja za tiste redke primere, ko temu ni tako.
Vrednosti 0, 0 sta odmika od levega (Left) in zgornjega (Top) roba na strani v skaliranem koordinatnem prostoru in ju pustite na nič, razen če obrezujete sliko. Vrednost ro0 predstavlja vrtenje: če jo pustite na nič, PDFium upošteva vrtenje, ki ga stran že določa v svojem vnosu /Rotate, zato se ležeče oblikovana stran izpiše ležeče, ne da bi morali storiti karkoli.
Kodiranje bitne slike v JPEG
Ko bitna slika obstaja, je shranjevanje v JPEG enostavnejši del in poteka v čistem Delphiju. Metoda TJPEGImage.Assign kopira bitno sliko, CompressionQuality nastavi kakovost na lestvici od 1 do 100, SaveToFile pa zapiše datoteko. Edino pravilo glede vrstnega reda je, da mora biti kakovost nastavljena pred shranjevanjem, saj določa kodiranje, ki ga sproži 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;
Ugnezdeni blok try/finally se morda zdi pretiran za pomočnika za eno stran, vendar je za paketno obdelavo nujen. Notranji blok sprosti kodirnik, zunanji blok sprosti bitno sliko, vsak od njiju pa ob sprožitvi izjeme še vedno sprosti tisto, kar ima v lasti. Če ju združite v enega, lahko izjema med kodiranjem povzroči, da bitna slika ostane v pomnilniku. Pri dolgotrajnem delovanju je to razlika med pretvornikom, ki uspešno zaključi delo, in tistim, ki se ustavi pri strani 300 s poškodovano datoteko in opozorilom o pomanjkanju pomnilnika.
Skupna izbira ločljivosti DPI in kakovosti
Ta dva parametra nista neodvisna od namena izhoda, pogosta napaka pa je, da iz previdnosti oba nastavimo na visoke vrednosti. Spletna sličica, izrisana pri 300 DPI in shranjena s kakovostjo 95, obsega nekaj sto kilobajtov, čeprav predstavlja le sliko velikosti 120 slikovnih pik; brskalnik ob pomanjšavi večino teh podatkov zavrže. Prilagodite ločljivost tistim slikovnim pikam, ki jih izhod dejansko potrebuje, nato pa izberite kakovost, ki preživi stiskanje JPEG brez opaznih popačenj.
| Izhod | DPI | Kakovost JPEG |
|---|---|---|
| Sličica seznama | 72 | 60-70 |
| Predogled na zaslonu | 96-150 | 80-85 |
| Pregledovanje z visokimi podrobnostmi | 200-300 | 85-95 |
| Tiskarska predloga | 300-600 | 90-100 |
Kakovost JPEG si sama po sebi zasluži nekaj opozoril. To ni linearna lestvica. Skok s 70 na 85 prinaša opazno vizualno izboljšavo ob zmernem povečanju datoteke; skok s 95 na 100 približno podvoji datoteko za razliko, ki je skoraj nihče ne opazi, saj kakovost 100 še vedno ni brezizgubna, le preneha zavreči večino podatkov. Pri straneh z veliko besedila stiskanje JPEG, ki temelji na blokih, zamegli ostre robove glifov v blago popačenje (ringing), zato kakovost pod 80 ustvari besedilo, ki je videti kot slabo skenirano, čeprav bi moralo biti ostro. Če so strani večinoma besedilne in lahko spremenite format, PNG izriše to besedilo brez teh popačenj; JPEG pa je primeren za fotografske in mešane vsebine, kjer je njegovo stiskanje resnično učinkovitejše.
Hitrejše in manjše sličice
Ko je cilj sličica in ne natančna reprodukcija, lahko izrisovalniku naročite, naj opravi manj dela. Parameter Options sprejme nabor zastavic TRenderOption, med katerimi nekatere zamenjajo natančnost za hitrost, ko je to primerno za majhen predogled. Zastavica reGrayscale odstrani barve, kar pospeši izris in ustvari manjšo bitno sliko za kodiranje. Zastavici reNoSmoothImage in reNoSmoothPath pa preskočita glajenje robov (anti-aliasing), ki je na ravni sličic tako ali tako nevidno.
function RenderThumbnail(Pdf: TPdf; PageNumber, MaxW, MaxH: Integer): TBitmap;
var
Scale: Double;
begin
Pdf.PageNumber := PageNumber;
// Fit the page inside MaxW x MaxH while preserving aspect ratio.
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;
Primer s sličicami kaže tudi čistejši način razmišljanja o določanju velikosti. Namesto uporabe DPI izračunajte en sam faktor skaliranja, ki stran stisne znotraj omejitvenega okvirja in ohrani razmerje stranic, kar stori funkcija Min med obema razmerjema. Pokončna in ležeča stran pristaneta v enakem okvirju brez popačenj, pri tem pa vam ni treba razmišljati, kateri DPI ustreza dimenziji »prilagodi v 200 × 280«. Uno opozorilo pri reGrayscale: pretvori vsebino rastrske slike v sivo, vendar vektorska polnila in besedilo v motorju ohranijo svoje barvne vrednosti, zato je lahko stran, ki je večinoma vektorska umetnost, manj črno-bela, kot nakazuje ime zastavice. Za pravi polni črno-beli rezultat je zanesljiva pot uporaba metode GrayscalePdfBitmap po izrisu.
Paketna obdelava celotnega dokumenta
Če vse skupaj združimo za celoten dokument, dobimo zanko čez PageCount, kjer se PageNumber premika za eno stran naenkrat. Indeksi strani se začnejo z 1: prva stran je PageNumber := 1, zanka pa teče do vključno PageCount in ne do PageCount - 1. Druga stvar, ki jo mora paketna obdelava spoštovati, je pogodba o tihem nalaganju. Nastavitev Active := True ob poškodovani datoteki ali napačnem geslu nikoli ne sproži izjeme, le pusti lastnost Active na False. To preverite pred izrisom katere koli strani, sicer bo prva metoda RenderPage delovala na dokumentu, ki sploh ni bil odprt.
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('Could not open ' + PdfPath);
Digits := Length(IntToStr(Pdf.PageCount)); // zero-pad so files sort right
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;
Dopolnjevanje z vodilnimi ničlami (zero-padding) s pomočjo spremenljivke Digits je malenkost, ki vam lahko kasneje prihrani veliko časa. Če datoteke poimenujete od page_1.jpg do page_10.jpg, bo katero koli orodje, ki jih razvršča kot nize, postavilo datoteko page_10 takoj za page_1, kar bo premešalo vrstni red. Dopolnjevanje do širine največje številke strani (tako da dokument s 300 stranmi ustvari datoteke, kot je page_001.jpg) ohranja leksikalni vrstni red in vrstni red strani povsod enak.
Pri dokumentih, ki so dovolj veliki, da pretvorba traja opazen čas, jo izvajajte izven niti uporabniškega vmesnika (UI thread) ali pa med stranmi osvežujte sporočila operacijskega sistema (pump messages), da aplikacija ostane odzivna, uporabniku pa ponudite možnost prekinitve. Če izrisujete zelo velike strani in želite možnost prekinitve sredi strani in ne le med stranmi, ponuja PDFium VCL progresivno pot izrisovanja z žetonom za prekinitev (cancellation token); to je zahtevnejši mehanizem, kot ga potrebuje večina paketnih izvozov, vendar je na voljo, ko je ena sama stran pri 600 DPI dovolj počasna, da blokira izvajanje.
In še zadnja povezava, ki jo je dobro poznati. Rasterizacija strani zavrže njeno plast besedila: slika JPEG je sestavljena iz slikovnih pik, besede v neh niso več izbirljive ali primerne za iskanje. Če potrebujete sliko in besedilo pod njo, izrišite sliko in ločeno izvlecite besedilo, kar obravnava spremljajoči članek o ekstrakciji besedila iz PDF dokumentov s PDFium VCL. Preobremenitve (overloads) metode RenderPage in možnosti izrisovanja, prikazane tukaj, so del komponente PDFium VCL Component za Delphi in C++Builder.