Prevod stránky PDF na obrázok JPEG predstavuje dve operácie, ktoré vývojári často spájajú dohromady a následne ladia oddelene. Najprv musíte stránku vykresliť (rasterizovať) do bitmapy v pixeloch pri zvolenom rozlíšení. Potom túto bitmapu odovzdáte kódovaču JPEG a zvolíte kvalitu. PDFium Component zabezpečuje prvú polovicu prostredníctvom metódy RenderPage; druhá polovica je čistou záležitosťou VCL, konkrétne triedy TJPEGImage z jednotky Vcl.Imaging.jpeg. Prepojenie medzi nimi je miestom, kde sa robia tie najzaujímavejšie rozhodnutia, pretože rozlíšenie zvolené pri vykresľovaní a kvalita vybraná pri kompresii sa navzájom ovplyvňujú a majú priamy vplyv na veľkosť súboru, v čom sa dá ľahko urobiť chyba
Pred napísaním akéhokoľvek kódu si treba uvedomiť: stránka PDF nemá žiadne pixely. Je popísaná v bodoch (points), kde jeden bod predstavuje 1/72 palca, a stránka je vlastne vektorový nákres meraný v týchto bodoch. Keď požiadate PDFium o vykreslenie, vyberáte si, na koľko pixelov sa má tento nákres premietnuť, a touto voľbou je hodnota DPI. Ak urobíte chybu v aritmetike, buď vykreslíte rozmazanú miniatúru namiesto tlačovej predlohy, alebo alokujete 200-megapixelovú bitmapu pre niečo, čo malo byť len náhľadom s veľkosťou 120 pixelov
Od DPI k rozmerom v pixeloch
Metóda RenderPage vyžaduje celočíselnú šírku (Width) a výšku (Height) v pixeloch, nie jednu hodnotu DPI. Prvou úlohou je teda prepočet. Stránka hlási svoju veľkosť v bodoch prostredníctvom vlastností PageWidth a PageHeight (obe typu Double) a prepočet je rovnaký, aký používa každý rasterizátor: pixely sa rovnajú bodom vynásobeným cieľovým DPI a vydeleným číslom 72. Stránka formátu US Letter má rozmery 612 × 792 bodov. Pri 150 DPI to predstavuje 1275 × 1650 pixelov; pri 72 DPI to zostáva 612 × 792, teda jeden pixel na jeden bod, čo je prípad, na ktorý ľudia často zabúdajú, že ide o identitu
// 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
Dva detaily v týchto štyroch riadkoch rozhodujú o tom, či je kód správny. Prvým je, že funkčná forma metódy RenderPage vracia objekt TBitmap, ktorý vlastníte vy. PDFium ho alokovalo a uvoľnilo; ak ho neuvoľníte (Free) v každej iterácii, hromadné spracovanie stoviek stránok spôsobí únik stoviek bitmap a proces sa bude nafukovať, kým nespadne. Druhým detailom je parameter Color, v tomto prípade clWhite. Stránky PDF sa zvyčajne kreslia s predpokladom nepriehľadného bieleho podkladu a vykreslenie stránky s priehľadnosťou na nesprávnu farbu pozadia vytvorí nepekné okraje alebo tmavé haló efekty. Biela farba je správnou predvolenou voľbou pre takmer každý dokument; tento parameter existuje pre zriedkavé prípady, keď to tak nie je
Hodnoty 0, 0 predstavujú odsadenie Left a Top na stránke v prepočítanom súradnicovom priestore a ponechávate ich na nule, pokiaľ nerobíte výrez. Hodnota ro0 predstavuje otočenie: ponechajte ju na nule a PDFium rešpektuje akékoľvek otočenie, ktoré stránka deklaruje vo svojom zázname /Rotate, takže stránka vytvorená na šírku vyjde na šírku bez toho, aby ste museli čokoľvek robiť
Kódovanie bitmapy do formátu JPEG
Keď už bitmapa existuje, kódovanie do JPEG je tá jednoduchšia časť a ide o čisté Delphi. Metóda TJPEGImage.Assign skopíruje bitmapu, CompressionQuality nastaví kvalitu na stupnici od 1 do 100 a SaveToFile zapíše súbor. Jediným pravidlom pre poradie krokov je, že kvalita musí byť nastavená pred uložením, pretože riadi kódovanie, ktoré SaveToFile spúšťa
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;
Tento vnorený blok try/finally vyzerá pre pomocnú funkciu pre jednu stránku prehnane zložito, ale pri hromadnom spracovaní je úplne na mieste. Vnútorný blok uvoľňuje kódovač, vonkajší blok uvoľňuje bitmapu a ktorýkoľvek z nich pri výskyte výnimky uvoľní to, čo vlastní. Zlúčte ich do jedného a výnimka počas kódovania môže zanechať bitmapu visieť v pamäti. Pri dlhom behu je to rozdiel medzi prevodníkom, ktorý úspešne dokončí prácu, a tým, ktorý zlyhá na 300. stránke s poškodeným súborom a chybovým hlásením o nedostatku pamäte
Spoločný výber DPI a kvality
Tieto dva ovládacie prvky nie sú nezávislé od účelu výstupu a častou chybou je nastavenie oboch hodnôt na maximum z opatrnosti. Miniatúra pre web vykreslená pri 300 DPI a uložená s kvalitou 95 má veľkosť niekoľko stoviek kilobajtov, hoci sa tvári ako obrázok s veľkosťou 120 pixelov; prehliadač pri zmenšení takmer všetky tieto dáta zahodí. Prispôsobte rozlíšenie pixelom, ktoré výstup skutočne potrebuje, a potom zvoľte kvalitu, ktorá prežije stratovú kompresiu JPEG bez viditeľných artekaktov
| Výstup | DPI | Kvalita JPEG |
|---|---|---|
| Miniatúra zoznamu | 72 | 60-70 |
| Náhľad na obrazovke | 96-150 | 80-85 |
| Detailné zobrazenie | 200-300 | 85-95 |
| Tlačová predloha | 300-600 | 90-100 |
Kvalita JPEG si zaslúži samostatné varovanie. Nie je to lineárne nastavenie. Skok zo 70 na 85 prináša skutočné vizuálne zlepšenie pri miernom nraste veľkosti súboru; skok z 95 na 100 zhruba zdvojnásobí veľkosť súboru pri rozdiele, ktorý takmer nikto neuvidí, pretože kvalita 100 stále nie je bezstratová, iba prestáva zahadzovať toľko dát. Pri stránkach s veľkým množstvom textu kompresia JPEG založená na blokoch rozmazáva ostré hrany glyfov do slabých „duchov“ (ringing), preto kvalita pod približne 80 robí text neostrým. Ak sú stránky prevažne textové a môžete zmeniť formát, PNG vykreslí tento text bez týchto artefaktov; JPEG si obháji svoje miesto pri fotografickom a zmiešanom obsahu, kde je jeho kompresia naozaj efektívnejšia
Rýchlejšie a menšie miniatúry
Ak je cieľom miniatúra a nie verná reprodukcia, môžete vykresľovaciemu jadru prikázať, aby robilo menej práce. Parameter Options prijíma sadu príznakov TRenderOption, pričom niektoré z nich vymieňajú vernosť za rýchlosť presne tak, ako to malý náhľad vyžaduje. Príznak reGrayscale odstráni farby, čo urýchli vykresľovanie a vytvorí menšiu bitmapu pre kódovanie. Príznaky reNoSmoothImage a reNoSmoothPath vynechajú vyhladzovanie, ktoré je pri veľkosti miniatúry aj tak nepostrehnuteľné
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;
Prípad s miniatúrou tiež ukazuje čistejší spôsob uvažovania o veľkosti. Namiesto prepočtu cez DPI vypočítajte jeden mierkový faktor, ktorý prispôsobí stránku do vymedzeného rámca pri zachovaní pomeru strán, čo robí funkcia Min z oboch pomerov. Stránka na výšku aj stránka na šírku tak skončia vo vnútri rovnakého rámca bez skreslenia a vy nemusíte uvažovať o tom, aké DPI zodpovedá rozmeru „prispôsobiť rozmeru 200 × 280“. Jedno upozornenie k príznaku reGrayscale: konvertuje rastrový obsah obrázkov na odtiene sivej, ale vektorové výplne a text si v jadre ponechávajú svoje farebné hodnoty, takže stránka, ktorá obsahuje prevažne vektorovú grafiku, môže vyjsť menej monochromatická, než napovedá názov príznaku. Pre skutočne plnohodnotný prevod do odtieňov sivej je spoľahlivou cestou metóda GrayscalePdfBitmap z prehľadu tipov
Hromadné spracovanie celého dokumentu
Spojenie všetkých častí pre celý dokument predstavuje cyklus cez PageCount, pričom sa hodnota PageNumber mení po jednej stránke. Stránky sú indexované od 1: prvá stránka má PageNumber := 1 a cyklus beží do PageCount vrátane, nie do PageCount - 1. Ďalšou vecou, ktorou musí hromadné spracovanie rešpektovať, je zmluva o tichom načítaní. Nastavenie Active := True nikdy nevyvolá výnimku pri poškodenom súbore alebo nesprávnom hesle; iba ponechá Active na hodnote False. Overte túto vlastnosť pred tým, než začnete vykresľovať čo i len jednu stránku, inak bude prvé volanie RenderPage pracovať s dokumentom, ktorý neobol nikdy úspešne otvorený
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;
Doplnenie núl na začiatku pomocou premennej Digits is a small thing that saves an afternoon later. Pomenujte súbory ako page_1.jpg až page_10.jpg, a akýkoľvek nástroj, ktorý ich usporadúva ako reťazce, zaradí page_10 hneď za page_1, čím pomieša poradie strán. Doplnenie núl na šírku najvyššieho čísla stránky (takže 300-stránkový dokument vygeneruje názvy od page_001.jpg) udrží lexikálne poradie a poradie stránok všade zhodné
Pri dokumentoch, ktoré sú dostatočne veľké na to, aby konverzia trvala dlhšie, spustite proces mimo hlavného vlákna používateľského rozhrania (UI thread) alebo spracovávajte správy systému medzi stránkami, aby aplikácia nezamŕzala, a poskytnite používateľovi možnosť proces zastaviť. Ak vykresľujete veľmi veľké stránky a vyžadujete možnosť zrušenia priamo uprostred vykresľovania stránky, PDFium Component ponúka cestu progresívneho vykresľovania s tokenom zrušenia; to je síce zložitejší mechanizmus než bežný hromadný export, ale v prípade potreby je k dispozícii
Posledné užitočné prepojenie. Rasterizácia stránky zahodí jej textovú vrstvu: výsledný JPEG sú len pixely a slová v ňom už nie je možné vyberať ani vyhľadávať. Keď potrebujete obrázok aj podkladový text, vykreslite obrázok a text vytiahnite samostatne, čomu sa venuje sprievodný článok o extrahovaní textu z PDF dokumentov pomocou PDFium Component. Preťaženia metódy RenderPage a možnosti vykresľovania uvedené v tomto článku sú súčasťou komponentu PDFium Component pre Delphi a C++Builder