Att rendera en PDF-sida till en JPEG är två operationer som folk tenderar att köra tillsammans och sedan felsöka (debug) separat. Först rastrerar du sidan till en pixel-bitmapp (pixel bitmap) i en upplösning du väljer. Sedan överlämnar du den bitmappen till en JPEG-kodare och väljer en kvalitet. PDFium Component äger den första halvan genom RenderPage; den andra halvan är ren VCL, TJPEGImage från Vcl.Imaging.jpeg. Sömmen mellan dem är där de intressanta besluten bor, eftersom den upplösning du väljer på renderingssidan och den kvalitet du väljer på kodningssidan kompromissas (trade off) mot varandra och mot filstorlek på sätt som är lätta att få fel
Saken att internalisera innan någon kod: en PDF-sida har inga pixlar. Den beskrivs i punkter (points), där en punkt är 1/72 tum, och sidan är en vektorritning mätt i dessa punkter. När du ber PDFium att rendera, väljer du hur många pixlar du vill projicera den ritningen på, och det valet är DPI (punkter per tum). Får du aritmetiken fel renderar du antingen en suddig miniatyrbild (thumbnail) när du ville ha ett tryckoriginal (print master), eller så allokerar du en 200-megapixels bitmapp för något som är avsett att vara en 120-pixels förhandsgranskning
Från DPI till pixeldimensioner
RenderPage vill ha heltalspixlar (integer pixel) Width och Height, inte ett DPI. Så det första jobbet är att konvertera. En sida rapporterar sin storlek i punkter genom PageWidth och PageHeight (båda Double), och konverteringen är densamma som varje rastrerare använder: pixlar är lika med punkter multiplicerat med mål-DPI delat med 72. En US Letter-sida är 612 x 792 punkter. Vid 150 DPI blir det 1275 x 1650 pixlar; vid 72 DPI stannar det på 612 x 792, en pixel per punkt, vilket är det fall folk glömmer bara är identiteten (the identity)
// 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
Två detaljer i de fyra raderna avgör om koden är korrekt. Den första är att funktionsformen av RenderPage returnerar en TBitmap som du äger. PDFium allokerade den och gick därifrån; om du inte frigör (Free) den i varje iteration, läcker en sats (batch) över några hundra sidor några hundra bitmappar och processen sväller (bloats) tills något rasar (falls over). Det andra är Color-argumentet, clWhite här. PDF-sidor ritas (drawn) vanligtvis under antagandet av ett opakt vitt underlag, och en sida med transparens renderad på fel bakgrundsfärg ger leriga (muddy) kanter eller enstaka mörka glorior (halos). Vitt är det rätta standardvärdet (default) för nästan varje dokument; parametern existerar för det sällsynta fallet där den inte är det
Dessa 0, 0 är Left- och Top-förskjutningarna (offsets) in i sidan, i den skalade koordinatrymden, och du lämnar dem på noll såvida du inte beskär (cropping). ro0 är rotation: lämna det på noll och PDFium respekterar vilken rotation sidan än redan deklarerar i sin /Rotate-post, så en sida som skapats i landskapsvy (landscape) kommer ut i landskapsvy utan att du gör något
Att koda bitmappen som JPEG
När bitmappen (bitmap) existerar är JPEG den enkla delen, och det är ren Delphi. TJPEGImage.Assign kopierar in bitmappen, CompressionQuality ställer in kvaliteten på en skala från 1 till 100, och SaveToFile skriver filen. Den enda ordningsregeln är att kvaliteten måste ställas in innan du sparar, eftersom den styr kodningen (the encode) som SaveToFile utlöser
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;
Den nästlade (nested) try/finally ser petig (fussy) ut för en ensideshjälpare, och den är exakt rätt för en satskörning (batch). Det inre blocket frigör kodaren, det yttre blocket frigör bitmappen, och om någotdera utlöser (firing) ett undantag (exception) släpps ändå det den äger. Kollapsa dem till ett, så kan ett undantag under kodning stranda (strand) bitmappen. Över en lång körning är det skillnaden mellan en konverterare som slutförs och en som dör på sida 300 med en korrupt fil och en slut-på-minne-dialog
Att välja DPI och kvalitet tillsammans
De två rattarna är inte oberoende av utdatans syfte, och det vanliga misstaget är att vrida upp båda av försiktighet. En webbminiatyr (web thumbnail) renderad i 300 DPI och sparad med kvalitet 95 är flera hundra kilobyte som låtsas vara en 120-pixels bild; webbläsaren kastar bort nästan allt vid nedskalningen. Matcha upplösningen till de pixlar som utdatan (output) faktiskt behöver, välj sedan en kvalitet som överlever JPEG:s förstörande (lossy) komprimering utan synliga artefakter
| Utmatning | DPI | JPEG-kvalitet |
|---|---|---|
| Listminiatyr | 72 | 60-70 |
| Förhandsgranskning på skärm | 96-150 | 80-85 |
| Detaljerad visning | 200-300 | 85-95 |
| Tryckoriginal | 300-600 | 90-100 |
JPEG-kvalitet är värd ett varningens ord (a word of caution) i sig. Den är inte en linjär ratt (linear dial). Hoppet från 70 till 85 köper en verklig visuell förbättring för en blygsam filtillväxt; hoppet från 95 till 100 fördubblar ungefär filen för en skillnad som nästan ingen kan se, eftersom kvalitet 100 fortfarande inte är förlustfri, den slutar bara kasta bort mycket. För texttunga sidor smetar (smears) JPEG:s blockbaserade komprimering ut de skarpa kanterna på glyfer till en svag ringning (faint ringing), vilket är anledningen till att kvalitet under cirka 80 gör att text ser skannad ut på vad som borde vara krispig utdata. Om sidorna mestadels är text och du kan byta format, renderar PNG den texten utan ringningen; JPEG förtjänar sin plats på fotografiskt och blandat innehåll där dess komprimering verkligen är mindre
Snabbare, mindre miniatyrbilder
När målet är en miniatyrbild snarare än en trogen reproduktion (faithful reproduction), kan du be renderaren att göra mindre arbete. Options-parametern tar en uppsättning TRenderOption-flaggor, och några av dem byter trohet (fidelity) mot hastighet på exakt det sätt en liten förhandsgranskning vill. reGrayscale släpper färgen, vilket både renderar snabbare och producerar en mindre bitmapp att koda. reNoSmoothImage och reNoSmoothPath hoppar över kantutjämning (anti-aliasing) som i alla fall är osynlig i miniatyrskala
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;
Miniatyrfallet visar också det renare sättet att tänka på dimensionering. Istället för att gå via DPI, beräkna en enda skalfaktor som anpassar sidan inuti en begränsningsruta (bounding box) och bevarar bildförhållandet (aspect ratio), vilket är vad Min av de två förhållandena gör. En stående sida och en liggande sida hamnar båda inuti samma ruta utan förvrängning, och du behöver aldrig resonera om vilket DPI som motsvarar "passa in i 200 gånger 280". Ett förbehåll med reGrayscale: den konverterar rasterbildsinnehåll till grått, men vektorfyllningar och text behåller sina färgvärden i motorn, så en sida som mestadels är vektorgrafik kan komma tillbaka mindre monokrom än vad flaggans namn antyder. För ett verkligt komplett gråskaleresultat är konvertering av den renderade bitmappen med GrayscalePdfBitmap den pålitliga vägen
Satskörning av ett helt dokument
Att sätta ihop det för ett fullständigt dokument är en loop över PageCount, med PageNumber flyttad en sida i taget. Sidor är 1-baserade: sida ett är PageNumber := 1, och loopen körs till PageCount inklusive, inte PageCount - 1. Den andra saken som satsen (the batch) måste respektera är tyst-laddning-kontraktet (silent-load contract). Att sätta Active := True utlöser (raises) aldrig för en skadad fil eller ett felaktigt lösenord; det lämnar bara Active på False. Kontrollera det innan du renderar en enda sida, annars arbetar den första RenderPage mot ett dokument som aldrig öppnades
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;
Nollutfyllnaden (zero-padding) genom Digits är en liten sak som sparar en eftermiddag senare. Namnge filerna page_1.jpg till page_10.jpg och alla verktyg som sorterar dem som strängar lägger page_10 precis efter page_1, och kastar om (scrambling) ordningen. Utfyllnad till bredden på det högsta sidnumret, så att ett 300-sidigt dokument ger page_001.jpg, håller lexikalisk ordning och sidordning identiska överallt nedströms (downstream)
För dokument stora nog att konverteringen tar märkbar (noticeable) tid, kör den utanför gränssnittstråden (UI thread) eller pumpa meddelanden mellan sidorna så applikationen förblir responsiv, och ge användaren ett sätt att avbryta (stop). Om du renderar mycket stora sidor och vill ha en avbrytningsfunktion (cancellation) som biter (bites) mitt i en sida (mid-page) snarare än bara mellan sidor, har PDFium Component en progressiv renderingsväg (progressive render path) med en avbrytnings-token (cancellation token); det är en tyngre mekanism än de flesta satsexporter (batch exports) behöver, men den finns där när en enskild sida vid 600 DPI i sig självt är långsam nog för att blockera
En sista parning (pairing) värd att känna till. Att rastrera en sida kastar (discards) dess textlager: JPEG är pixlar, och orden i den är inte längre valbara (selectable) eller sökbara. När du behöver både en bild och den underliggande texten, renderar du för bilden och drar ut (pull) texten separat, vilket den medföljande biten (companion piece) om att extrahera text från PDF-dokument med PDFium Component täcker. De RenderPage-överlagringar (overloads) och renderingsalternativ som visas här är en del av PDFium Component för Delphi och C++Builder