Randarea unei pagini PDF într-un JPEG înseamnă două operații pe care lumea tinde să le ruleze împreună și apoi să le depaneze separat. Întâi rasterizați pagina într-o imagine în pixeli, la o rezoluție aleasă de dumneavoastră. Apoi dați acea imagine unui codificator JPEG și alegeți o calitate. PDFium Component deține prima jumătate prin RenderPage; a doua jumătate este VCL curat, TJPEGImage din Vcl.Imaging.jpeg. Cusătura dintre ele este locul în care stau deciziile interesante, pentru că rezoluția aleasă la randare și calitatea aleasă la codare se compensează reciproc și se compensează cu dimensiunea fișierului în feluri ușor de greșit
Lucrul de interiorizat înaintea oricărui cod: o pagină PDF nu are pixeli. Ea este descrisă în puncte, unde un punct înseamnă 1/72 dintr-un țol, iar pagina este un desen vectorial măsurat în acele puncte. Când îi cereți lui PDFium să randeze, alegeți pe câți pixeli să proiecteze acel desen, iar acea alegere este DPI-ul. Greșiți aritmetica și fie randați o miniatură neclară când voiați un original de tipar, fie alocați o imagine de 200 de megapixeli pentru ceva sortit să fie o previzualizare de 120 de pixeli
De la DPI la dimensiuni în pixeli
RenderPage vrea Width și Height întregi, în pixeli, nu un DPI. Așa că prima treabă este conversia. O pagină își raportează dimensiunea în puncte prin PageWidth și PageHeight (ambele Double), iar conversia este aceeași pe care o folosește orice rasterizor: pixelii sunt egali cu punctele înmulțite cu DPI-ul țintă și împărțite la 72. O pagină US Letter are 612 pe 792 de puncte. La 150 DPI devine 1275 pe 1650 de pixeli; la 72 DPI rămâne 612 pe 792, un pixel per punct, cazul despre care lumea uită că este doar identitatea
// Pdf.PageNumber trebuie să indice deja pagina dorită.
PixelW := Round(Pdf.PageWidth * Dpi / 72);
PixelH := Round(Pdf.PageHeight * Dpi / 72);
Bitmap := Pdf.RenderPage(0, 0, PixelW, PixelH, ro0, [], clWhite);
// ... folosește Bitmap ...
Bitmap.Free; // forma-funcție a lui RenderPage îți predă proprietatea
Două detalii din acele patru rânduri decid dacă acest cod este corect. Primul este că forma-funcție a lui RenderPage returnează un TBitmap pe care îl dețineți dumneavoastră. PDFium l-a alocat și a plecat; dacă nu îl eliberați cu Free la fiecare iterație, un lot de câteva sute de pagini pierde câteva sute de imagini, iar procesul se umflă până cedează ceva. Al doilea este argumentul Color, aici clWhite. Paginile PDF sunt de obicei desenate presupunând un substrat alb opac, iar o pagină cu transparență randată peste culoarea de fundal greșită produce margini tulburi sau halouri întunecate rătăcite. Albul este valoarea implicită potrivită pentru aproape orice document; parametrul există pentru cazul rar în care nu este
Perechea 0, 0 reprezintă deplasările Left și Top în pagină, în spațiul de coordonate scalat, iar dumneavoastră le lăsați la zero dacă nu decupați. ro0 este rotirea: lăsați-o pe zero și PDFium respectă orice rotire declară deja pagina în intrarea ei /Rotate, așa că o pagină creată în format vedere iese în format vedere fără să faceți nimic
Codarea imaginii bitmap ca JPEG
Odată ce imaginea bitmap există, JPEG-ul este partea ușoară și este Delphi pur. TJPEGImage.Assign copiază imaginea înăuntru, CompressionQuality setează calitatea pe o scală de la 1 la 100, iar SaveToFile scrie fișierul. Singura regulă de ordine este că trebuie să setați calitatea înainte de salvare, pentru că ea guvernează codarea pe care o declanșează 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;
Acel try/finally imbricat pare pedant pentru o rutină de o pagină și este exact ce trebuie pentru un lot. Blocul interior eliberează codificatorul, cel exterior eliberează imaginea, iar oricare dintre ele, declanșat de o excepție, tot eliberează ce deține. Contopiți-le într-unul singur și o excepție în timpul codării poate abandona imaginea. Pe o rulare lungă, asta este diferența dintre un convertor care ajunge la capăt și unul care moare la pagina 300 cu un fișier corupt și o casetă de memorie insuficientă
Alegerea împreună a DPI-ului și a calității
Cele două butoane nu sunt independente de scopul rezultatului, iar greșeala obișnuită este să le dați pe amândouă la maximum, din prudență. O miniatură pentru web randată la 300 DPI și salvată la calitatea 95 înseamnă câteva sute de kiloocteți care se prefac a fi o imagine de 120 de pixeli; browserul aruncă aproape tot la micșorare. Potriviți rezoluția cu pixelii de care are nevoie efectiv rezultatul, apoi alegeți o calitate care supraviețuiește compresiei cu pierderi a JPEG-ului fără artefacte vizibile
| Rezultat | DPI | Calitate JPEG |
|---|---|---|
| Miniatură în listă | 72 | 60-70 |
| Previzualizare pe ecran | 96-150 | 80-85 |
| Vizionare cu detaliu mare | 200-300 | 85-95 |
| Original pentru tipar | 300-600 | 90-100 |
Calitatea JPEG merită o avertizare separată. Nu este un buton liniar. Saltul de la 70 la 85 cumpără o îmbunătățire vizuală reală la o creștere modestă a fișierului; saltul de la 95 la 100 aproape dublează fișierul pentru o diferență pe care aproape nimeni nu o vede, pentru că nici calitatea 100 nu este fără pierderi, doar încetează să mai arunce mult. La paginile pline de text, compresia pe blocuri a JPEG-ului întinde marginile ascuțite ale glifelor într-o umbră slabă, motiv pentru care o calitate sub aproximativ 80 face ca textul să pară scanat acolo unde rezultatul ar trebui să fie clar. Dacă paginile sunt mai ales text și puteți schimba formatul, PNG randează acel text fără umbră; JPEG își merită locul pe conținut fotografic și mixt, unde compresia lui este într-adevăr mai mică
Miniaturi mai rapide și mai mici
Când ținta este o miniatură, nu o reproducere fidelă, îi puteți spune motorului de randare să muncească mai puțin. Parametrul Options primește un set de indicatori TRenderOption, iar câțiva dintre ei schimbă fidelitatea pe viteză exact așa cum vrea o previzualizare mică. reGrayscale renunță la culoare, ceea ce și randează mai repede, și produce o imagine mai mică de codat. reNoSmoothImage și reNoSmoothPath sar peste antialiasingul oricum invizibil la scară de miniatură
function RenderThumbnail(Pdf: TPdf; PageNumber, MaxW, MaxH: Integer): TBitmap;
var
Scale: Double;
begin
Pdf.PageNumber := PageNumber;
// Încadrează pagina în MaxW x MaxH păstrând raportul de aspect.
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;
Cazul miniaturii arată și modul mai curat de a gândi dimensionarea. În loc să treceți prin DPI, calculați un singur factor de scalare care încadrează pagina într-o casetă și păstrează raportul de aspect, adică exact ce face Min-ul celor două rapoarte. O pagină portret și una în format vedere ajung amândouă în aceeași casetă fără distorsiune, iar dumneavoastră nu trebuie niciodată să vă gândiți ce DPI corespunde lui „încape în 200 pe 280”. O rezervă la reGrayscale: el convertește conținutul de imagine raster în gri, dar umplerile vectoriale și textul își păstrează valorile de culoare în motor, așa că o pagină formată mai ales din grafică vectorială poate ieși mai puțin monocromă decât sugerează numele indicatorului. Pentru un rezultat cu adevărat integral în tonuri de gri, conversia imaginii randate cu GrayscalePdfBitmap este drumul de încredere
Prelucrarea în lot a unui document întreg
Punerea laolaltă pentru un document complet înseamnă o buclă peste PageCount, cu PageNumber mutat câte o pagină pe rând. Paginile sunt indexate de la 1: prima pagină este PageNumber := 1, iar bucla merge până la PageCount inclusiv, nu până la PageCount - 1. Celălalt lucru pe care lotul trebuie să îl respecte este contractul de încărcare tăcută. Setarea lui Active := True nu ridică niciodată excepție la un fișier deteriorat sau la o parolă greșită; pur și simplu lasă Active pe False. Verificați-l înainte să randați vreo pagină, altfel primul RenderPage lucrează pe un document care nu a fost deschis niciodată
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)); // completează cu zerouri, ca fișierele să se sorteze corect
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;
Completarea cu zerouri prin Digits este un fleac care vă scutește de o după-amiază pierdută mai târziu. Numiți fișierele de la page_1.jpg la page_10.jpg și orice instrument care le sortează ca șiruri pune page_10 imediat după page_1, încurcând ordinea. Completarea până la lățimea celui mai mare număr de pagină, așa încât un document de 300 de pagini să producă page_001.jpg, păstrează ordinea lexicală identică cu ordinea paginilor peste tot mai departe
Pentru documentele destul de mari cât conversia să dureze sensibil, rulați-o în afara firului de interfață sau pompați mesaje între pagini, ca aplicația să rămână receptivă, și dați utilizatorului o cale de a opri. Dacă randați pagini foarte mari și vreți o anulare care mușcă în mijlocul paginii, nu doar între pagini, PDFium Component are un drum de randare progresivă cu un jeton de anulare; acela este un mecanism mai greu decât are nevoie majoritatea exporturilor în lot, dar există pentru când o singură pagină la 600 DPI este ea însăși destul de lentă cât să blocheze
O ultimă împerechere care merită știută. Rasterizarea unei pagini îi aruncă stratul de text: JPEG-ul este pixeli, iar cuvintele din el nu mai pot fi selectate sau căutate. Când aveți nevoie și de imagine, și de textul de dedesubt, randați pentru imagine și extrageți textul separat, ceea ce acoperă articolul însoțitor despre extragerea textului din documente PDF cu PDFium Component. Supraîncărcările RenderPage și opțiunile de randare arătate aici fac parte din PDFium Component pentru Delphi și C++Builder