PDF puslapio atvaizdavimas į JPEG yra dvi skirtingos operacijos, kurias programuotojai linkę sujungti į vieną ir vėliau derinti atskirai. Pirmiausia atliekamas puslapio rasterizavimas į pikselių taškinį vaizdą (bitmap) pasirinkta skiriamąja geba. Tada šis taškinis vaizdas perduodamas JPEG kodavimo įrankiui ir nustatoma kokybė. „PDFium VCL“ atlieka pirmąją dalį per metodą RenderPage, o antroji dalis atliekama naudojant standartinį VCL komponentą TJPEGImage iš Vcl.Imaging.jpeg. Jungtis tarp šių dviejų dalių reikalauja apgalvotų sprendimų, nes pasirinkta atvaizdavimo skiriamoji geba ir kodavimo kokybė tiesiogiai veikia viena kitą bei galutinį failo dydį.
Prieš pradedant rašyti kodą, svarbu suprasti vieną dalyką: PDF puslapis neturi pikselių. Jis aprašomas taškais (points), kur vienas taškas yra 1/72 colio dalis, o pats puslapis yra vektorinis piešinys, matuojamas šiais taškais. Kai nurodote PDFium atlikti atvaizdavimą, pasirenkate, į kiek pikselių projektuoti šį piešinį, ir šis pasirinkimas yra DPI (taškai colyje). Jei neteisingai apskaičiuosite matmenis, gausite arba neryškią miniatiūrą vietoj spaudai tinkamo vaizdo, arba išskirsite atmintį 200 megapikselių taškiniam vaizdui, kuris turėjo būti tik 120 pikselių peržiūros piktograma.
Nuo DPI iki pikselių matmenų
Metodui RenderPage reikalingi sveikojo skaičiaus tipo pikselių matmenys Width (plotis) ir Height (aukštis), o ne DPI reikšmė. Todėl pirmiausia reikia atlikti konvertavimą. Puslapis pateikia savo dydį taškais per savybes PageWidth ir PageHeight (abi yra Double tipo), o konvertavimas yra standartinis: pikseliai yra lygūs taškams, padaugintiems iš tikslinio DPI ir padalintiems iš 72. Pavyzdžiui, JAV laiškų (US Letter) puslapis yra 612 × 792 taškų. Esant 150 DPI, tai virsta 1275 × 1650 pikselių, o esant 72 DPI – lieka 612 × 792 pikselių (vienas pikselis vienam taškui).
// 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
Dvi detalės šiose keliose eilutėse nustato, ar kodas veiks teisingai. Pirmoji yra ta, kad funkcinė RenderPage forma grąžina TBitmap objektą, kurio savininku tampate jūs. PDFium jį išskiria atmintyje ir palieka valdyti jums. Jei kiekvienoje iteracijoje neatlaisvinsite jo su Free, apdorojant kelis šimtus puslapių bus nutekinti keli šimtai taškinių vaizdų, o procesas plėsis, kol programa užstrigs. Antroji detalė – spalvos argumentas clWhite. PDF puslapiai paprastai atvaizduojami darant prielaidą, kad jų pagrindas yra nepermatomas baltas, todėl puslapis su permatomumu, atvaizduotas netinkama fono spalva, gali turėti neryškius kraštus arba tamsius kontūrus. Balta spalva yra tinkama numatytoji parinktis beveik kiekvienam dokumentui; parametras egzistuoja tam, kad prireikus būtų galima nustatyti kitą spalvą.
Reikšmės 0, 0 yra puslapio postūmiai Left (iš kairės) ir Top (iš viršaus) pakeisto mastelio koordinačių erdvėje. Juos paliekate lygius nuliui, nebent norite apkarpyti vaizdą. ro0 nurodo pasukimą: palikite jį ties nuliu ir PDFium atsižvelgs į puslapio nustatymuose jau deklaruotą pasukimą (įrašas /Rotate), todėl gulsčias puslapis bus automatiškai atvaizduotas gulsčiai.
Taškinio vaizdo kodavimas į JPEG
Kai taškinis vaizdas sukurtas, konvertavimas į JPEG yra paprasta užduotis, atliekama grynai „Delphi“ priemonėmis. Metodas TJPEGImage.Assign nukopijuoja taškinį vaizdą, CompressionQuality nustato kokybę nuo 1 iki 100 balų skalėje, o SaveToFile įrašo failą. Vienintelė taisyklė – kokybę turite nustatyti prieš išsaugodami failą, nes ji nulemia kodavimo procesą, kurį paleidžia 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;
Ši įdėta try/finally konstrukcija gali atrodyti perteklinė vieno puslapio apdorojimui, tačiau ji yra būtina paketiniam konvertavimui. Vidinis blokas atlaisvina kodavimo įrankį, o išorinis – taškinį vaizdą. Net jei įvyktų išimtis, abu objektai bus tinkamai pašalinti iš atminties. Sujungus juos į vieną bloką, išimtis kodavimo metu gali palikti taškinį vaizdą atmintyje. Apdorojant didelį kiekį failų, tai yra skirtumas tarp sėkmingai baigto darbo ir programos avarinio sustojimo ties 300-uoju puslapiu dėl atminties trūkumo.
DPI ir kokybės derinimas
Šie du nustatymai priklauso nuo to, kam bus naudojamas rezultatas, ir dažna klaida yra abiejų reikšmių padidinimas „dėl atsargumo“. Interneto miniatiūra, atvaizduota 300 DPI skiriamąja geba ir išsaugota 95 kokybe, užims kelis šimtus kilobaitų, nors realiai bus rodoma kaip 120 pikselių vaizdas – naršyklė tiesiog atmes didžiąją dalį informacijos keisdama mastelį. Parinkite skiriamąją gebą pagal tai, kiek pikselių iš tikrųjų reikia, o tada pasirinkite kokybę, kuri apsaugo nuo matomų suspaudimo artefaktų.
| Paskirtis | DPI | JPEG kokybė |
|---|---|---|
| Sąrašo miniatiūra | 72 | 60-70 |
| Peržiūra ekrane | 96-150 | 80-85 |
| Detali peržiūra | 200-300 | 85-95 |
| Spaudos šablonas | 300-600 | 90-100 |
JPEG kokybės nustatymas reikalauja atskiro dėmesio. Tai nėra linijinis parametras. Perėjimas nuo 70 iki 85 kokybės pastebimai pagerina vaizdą nedidinant failo dydžio; tuo tarpu perėjimas nuo 95 iki 100 kokybės gali padvigubinti failo dydį, nors vizualinio skirtumo beveik nebus, kadangi 100 kokybė vis tiek nėra be nuostolių (lossless). Puslapiuose, kuriuose gausu teksto, JPEG blokinis suspaudimas gali išlieti aštrius simbolių kraštus, todėl nustačius žemesnę nei 80 kokybę, tekstas gali atrodyti nekokybiškas. Jei puslapiuose daugiausia yra teksto ir galite keisti failų formatą, PNG atvaizduos tekstą be jokių išsiliejimų, o JPEG geriausiai tinka nuotraukoms arba mišriam turiniui, kur jo suspaudimas yra efektyvesnis.
Greitesnis mažesnių miniatiūrų kūrimas
Kai tikslas yra sukurti miniatiūrą, o ne tikslią kopiją, galite nurodyti atvaizdavimo varikliui atlikti mažiau darbo. Parametras Options priima TRenderOption vėliavėlių aibę, ir kai kurios iš jų leidžia paaukoti detalumą vardan greičio. Pavyzdžiui, vėliavėlė reGrayscale pašalina spalvas, todėl atvaizdavimas vyksta greičiau ir sukuriamas mažesnis taškinis vaizdas. Vėliavėlės reNoSmoothImage ir reNoSmoothPath išjungia glotninimą (anti-aliasing), kuris miniatiūros masteliu vis tiek yra nepastebimas.
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;
Miniatiūrų kūrimas taip pat parodo paprastesnį būdą nustatyti matmenis. Užuot skaičiavę pagal DPI, nustatykite vieną mastelio koeficientą, kuris sutalpina puslapį į rėmus išlaikant kraštinių santykį (būtent tai atlieka funkcija Min su abiem koeficientais). Tiek stačias, tiek gulsčias puslapis bus sutalpintas į tą pačią sritį be jokių iškraipymų, ir jums nereikės sukti galvos, koks DPI atitinka matmenis „200 × 280“. Viena pastaba dėl reGrayscale: ji paverčia rastrinį turinį pilku, tačiau vektoriniai užpildai ir tekstas variklyje gali išlaikyti savo spalvų reikšmes, todėl puslapis, kuriame gausu vektorinės grafikos, gali iš dalies išlikti spalvotas. Norint gauti visiškai nespalvotą vaizdą, patikimiausias būdas yra atlikti papildomą taškinio vaizdo apdorojimą po jo atvaizdavimo.
Viso dokumento apdorojimas paketu
Norint pritaikyti tai visam dokumentui, reikia paleisti ciklą per PageCount, keičiant PageNumber reikšmę po vieną puslapį. Puslapių indeksavimas prasideda nuo 1: pirmasis puslapis yra PageNumber := 1, o ciklas vykdomas iki PageCount įskaitytinai (ne iki PageCount - 1). Taip pat svarbu atsižvelgti į dokumento įkėlimo ypatumą: savybės Active := True nustatymas nesukelia klaidos, jei failas pažeistas arba slaptažodis neteisingas – savybė tiesiog lieka False. Patikrinkite šią būseną prieš pradėdami atvaizdavimą, kitaip pirmoji RenderPage funkcija veiks su neatidarytu dokumentu.
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;
Skaitmenų papildymas nuliais naudojant Digits is smulkmena, kuri vėliau sutaupys daug laiko. Jei pavadinsite failus nuo page_1.jpg iki page_10.jpg, bet kuris įrankis, rūšiuojantis juos kaip eilutes, įdės page_10 iškart po page_1, taip sumaišydamas puslapių tvarką. Užpildžius pavadinimus nuliais iki maksimalaus puslapių skaičiaus ilgio (pavyzdžiui, page_001.jpg 300 puslapių dokumentui), leksinė ir puslapių tvarka sutaps visose vėlesnėse sistemose.
Apdorojant didelius dokumentus, kai konvertavimas užtrunka, vykdykite procesą ne pagrindinėje sąsajos gijoje arba apdorokite pranešimus tarp puslapių, kad programa išliktų reaguojanti ir naudotojas galėtų ją sustabdyti. Jei atvaizduojate labai didelius puslapius ir norite suteikti galimybę atšaukti procesą viduryje puslapio, „PDFium VCL“ siūlo progresyvų atvaizdavimą su atšaukimo žetonu (cancellation token). Tai yra sudėtingesnis mechanizmas nei įprastas paketinis eksportas, tačiau jis praverčia, kai vieno puslapio atvaizdavimas 600 DPI skiriamąja geba gali užblokuoti sistemą.
Paskutinė naudinga detalė: puslapio rasterizavimas pašalina jo tekstinį sluoksnį – JPEG vaizdą sudaro tik pikseliai, todėl jame esantys žodžiai nebėra pasirenkami ar ieškomi. Kai jums reikia ir vaizdo, ir teksto, atvaizduokite vaizdą ir atskirai išgaukite tekstą, kaip aprašyta straipsnyje apie teksto išgavimą iš PDF dokumentų naudojant PDFium VCL. Čia parodyti RenderPage metodai ir nustatymai yra dalis „Delphi“ ir „C++Builder“ skirtos bibliotekos PDFium VCL Component.