Een PDF-pagina naar een JPEG renderen bestaat uit twee bewerkingen die men geneigd is samen uit te voeren en daarna apart te debuggen. Eerst rasteriseert u de pagina naar een pixelbitmap op een resolutie die u kiest. Daarna geeft u die bitmap aan een JPEG-encoder en kiest u een kwaliteit. PDFium Component bezit de eerste helft via RenderPage; de tweede helft is gewone VCL, TJPEGImage uit Vcl.Imaging.jpeg. De naad ertussen is waar de interessante beslissingen wonen, want de resolutie die u aan de renderkant kiest en de kwaliteit die u aan de codeerkant kiest, wegen tegen elkaar en tegen de bestandsgrootte op manieren die makkelijk fout gaan
Wat u vóór alle code moet verinnerlijken: een PDF-pagina heeft geen pixels. Zij wordt beschreven in punten, waarbij één punt 1/72 inch is, en de pagina is een vectortekening gemeten in die punten. Wanneer u PDFium vraagt te renderen, kiest u op hoeveel pixels die tekening wordt geprojecteerd, en die keuze is de DPI. Reken verkeerd en u rendert ofwel een wazige miniatuur terwijl u een afdrukmaster wilde, ofwel wijst u een bitmap van 200 megapixel toe voor iets wat een voorbeeld van 120 pixels moest worden
Van DPI naar pixelafmetingen
RenderPage wil een geheeltallige Width en Height in pixels, geen DPI. De eerste klus is dus omrekenen. Een pagina meldt haar formaat in punten via PageWidth en PageHeight (beide Double), en de omrekening is dezelfde die elke rasteriseerder gebruikt: pixels zijn punten maal de doel-DPI gedeeld door 72. Een pagina op US Letter is 612 bij 792 punten. Op 150 DPI wordt dat 1275 bij 1650 pixels; op 72 DPI blijft het 612 bij 792, één pixel per punt, het geval waarvan men vergeet dat het gewoon de identiteit is
// Pdf.PageNumber moet al naar de gewenste pagina wijzen.
PixelW := Round(Pdf.PageWidth * Dpi / 72);
PixelH := Round(Pdf.PageHeight * Dpi / 72);
Bitmap := Pdf.RenderPage(0, 0, PixelW, PixelH, ro0, [], clWhite);
// ... gebruik Bitmap ...
Bitmap.Free; // de functievorm van RenderPage draagt het eigendom aan u over
Twee details in die vier regels bepalen of de code correct is. Het eerste is dat de functievorm van RenderPage een TBitmap teruggeeft waarvan u de eigenaar bent. PDFium heeft hem toegewezen en is doorgelopen; roept u niet bij elke iteratie Free aan, dan lekt een batch over een paar honderd pagina's evenzoveel bitmaps en zwelt het proces aan tot er iets omvalt. Het tweede is het argument Color, hier clWhite. PDF-pagina's worden meestal getekend in de veronderstelling van een ondoorzichtige witte ondergrond, en een pagina met transparantie die op de verkeerde achtergrondkleur wordt gerenderd, levert modderige randen of vreemde donkere halo's op. Wit is voor vrijwel elk document de juiste standaard; de parameter bestaat voor het zeldzame geval dat dat niet zo is
De 0, 0 zijn de verschuivingen Left en Top in de pagina, in de geschaalde coördinatenruimte, en die laat u op nul staan tenzij u bijsnijdt. De ro0 is rotatie: laat die op nul en PDFium eert de rotatie die de pagina al in haar vermelding /Rotate verklaart, zodat een pagina die in liggend formaat is opgesteld ook liggend naar buiten komt zonder dat u iets doet
De bitmap als JPEG coderen
Zodra de bitmap bestaat, is JPEG het makkelijke deel, en het is puur Delphi. TJPEGImage.Assign kopieert de bitmap erin, CompressionQuality zet de kwaliteit op een schaal van 1 tot 100, en SaveToFile schrijft het bestand. De enige volgorderegel is dat de kwaliteit moet zijn gezet voordat u opslaat, want zij bestuurt de codering die SaveToFile in gang zet
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;
Die geneste try/finally oogt pietluttig voor een hulpfunctie van één pagina, en zij is precies goed voor een batch. Het binnenste blok geeft de encoder vrij, het buitenste blok geeft de bitmap vrij, en welk van beide ook bij een uitzondering afgaat, het geeft nog steeds vrij wat het bezit. Vouw ze samen tot één en een uitzondering tijdens het coderen kan de bitmap laten stranden. Over een lange run is dat het verschil tussen een converter die afrondt en een die op pagina 300 sterft met een corrupt bestand en een dialoogvenster over onvoldoende geheugen
DPI en kwaliteit samen kiezen
De twee knoppen staan niet los van het doel van de uitvoer, en de gangbare fout is beide uit voorzichtigheid opendraaien. Een webminiatuur die op 300 DPI wordt gerenderd en op kwaliteit 95 wordt opgeslagen, is enkele honderden kilobytes die zich voordoen als een afbeelding van 120 pixels; de browser gooit bij het verkleinen bijna alles weg. Stem de resolutie af op de pixels die de uitvoer werkelijk nodig heeft, en kies daarna een kwaliteit die de verliesgevende compressie van JPEG doorstaat zonder zichtbare artefacten
| Uitvoer | DPI | JPEG-kwaliteit |
|---|---|---|
| Miniatuur in een lijst | 72 | 60-70 |
| Voorbeeld op het scherm | 96-150 | 80-85 |
| Bekijken met veel detail | 200-300 | 85-95 |
| Afdrukmaster | 300-600 | 90-100 |
De JPEG-kwaliteit verdient op zichzelf een waarschuwend woord. Het is geen lineaire schuifknop. De sprong van 70 naar 85 koopt een echte visuele verbetering voor bescheiden bestandsgroei; de sprong van 95 naar 100 verdubbelt het bestand ongeveer voor een verschil dat vrijwel niemand ziet, want kwaliteit 100 is nog altijd niet verliesvrij, het houdt alleen op veel weg te gooien. Bij pagina's met veel tekst smeert de blokgewijze compressie van JPEG de scherpe randen van lettertekens uit tot zwakke rinkeling, en daarom laat een kwaliteit onder ongeveer 80 tekst er gescand uitzien terwijl de uitvoer knisperend hoorde te zijn. Bestaan de pagina's vooral uit tekst en kunt u van formaat wisselen, dan rendert PNG die tekst zonder de rinkeling; JPEG verdient zijn plaats bij fotografische en gemengde inhoud, waar zijn compressie werkelijk kleiner uitvalt
Snellere, kleinere miniaturen
Wanneer het doel een miniatuur is en geen getrouwe reproductie, kunt u de renderer opdragen minder werk te doen. De parameter Options neemt een verzameling vlaggen van het type TRenderOption, en enkele daarvan ruilen precies op de manier die een klein voorbeeld wenst getrouwheid in voor snelheid. reGrayscale laat kleur vallen, wat zowel sneller rendert als een kleinere bitmap oplevert om te coderen. reNoSmoothImage en reNoSmoothPath slaan anti-aliasing over die op miniatuurschaal toch onzichtbaar is
function RenderThumbnail(Pdf: TPdf; PageNumber, MaxW, MaxH: Integer): TBitmap;
var
Scale: Double;
begin
Pdf.PageNumber := PageNumber;
// Pas de pagina binnen MaxW x MaxH met behoud van de beeldverhouding.
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;
Het miniatuurgeval laat ook de schonere manier zien om over dimensionering te denken. In plaats van via DPI te gaan, berekent u één schaalfactor die de pagina binnen een omhullend kader past en de beeldverhouding bewaart, en dat is wat de Min van de twee verhoudingen doet. Een staande en een liggende pagina belanden allebei zonder vervorming binnen hetzelfde kader, en u hoeft nooit te beredeneren welke DPI overeenkomt met "pas in 200 bij 280". Eén kanttekening bij reGrayscale: de vlag zet rasterafbeeldingen om naar grijs, maar vectorvullingen en tekst behouden in de engine hun kleurwaarden, dus een pagina die vooral uit vectorwerk bestaat, kan minder monochroom terugkomen dan de naam van de vlag suggereert. Voor een werkelijk volledig grijswaardenresultaat is de gerenderde bitmap omzetten met GrayscalePdfBitmap het betrouwbare pad
Een heel document in batch verwerken
Alles bij elkaar voor een volledig document is een lus over PageCount, waarbij PageNumber per pagina opschuift. Pagina's zijn 1-gebaseerd: pagina één is PageNumber := 1, en de lus loopt tot en met PageCount, niet tot PageCount - 1. Het andere dat de batch moet respecteren, is het contract van het stille laden. Active := True zetten werpt bij een beschadigd bestand of een verkeerd wachtwoord nooit iets op; het laat Active gewoon op False staan. Controleer dat voordat u één pagina rendert, anders werkt de eerste RenderPage tegen een document dat nooit is geopend
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)); // vul met nullen zodat bestanden goed sorteren
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;
Het opvullen met nullen via Digits is een kleinigheid die u later een middag bespaart. Noem de bestanden page_1.jpg tot en met page_10.jpg en elk gereedschap dat ze als tekenreeksen sorteert, zet page_10 direct achter page_1 en gooit de volgorde door elkaar. Opvullen tot de breedte van het hoogste paginanummer, zodat een document van 300 pagina's page_001.jpg oplevert, houdt de lexicale volgorde en de paginavolgorde overal verderop identiek
Voor documenten die groot genoeg zijn dat de conversie merkbaar tijd kost, draait u die buiten de UI-thread of pompt u tussen pagina's berichten door zodat de applicatie reactief blijft, en geeft u de gebruiker een manier om te stoppen. Rendert u zeer grote pagina's en wilt u annulering die midden in een pagina aangrijpt in plaats van alleen ertussen, dan heeft PDFium Component een progressief renderpad met een annuleringstoken; dat is een zwaarder mechanisme dan de meeste batchexports nodig hebben, maar het is er wanneer één pagina op 600 DPI zelf al traag genoeg is om te blokkeren
Nog één combinatie is het weten waard. Een pagina rasteriseren gooit haar tekstlaag weg: de JPEG is pixels, en de woorden erin zijn niet langer selecteerbaar of doorzoekbaar. Hebt u zowel een afbeelding als de onderliggende tekst nodig, render dan voor de afbeelding en haal de tekst er apart uit, wat het begeleidende stuk over tekst uit PDF-documenten halen met PDFium Component behandelt. De hier getoonde overloads van RenderPage en de renderopties maken deel uit van de PDFium Component voor Delphi en C++Builder