PDF čuva slike kao objekte prvog reda unutar svojih tokova sadržaja (content streams). Kada stranica referencira fotografiju, skeniranu sliku ili dijagram, podaci o pikselima žive u XObject rečniku pored geometrije same stranice. PDFium Component to izlaže preko dva svojstva na TPdf komponenti: BitmapCount, koje vraća koliko se ugrađenih bitmapa nalazi na trenutnoj stranici, i Bitmap[Index], koje dekodira jednu od njih u TBitmap nad kojim preuzimate vlasništvo i koji morate osloboditi. To je ceo model ekstrakcije. Petlja ima četiri reda; ono što zahteva procenu jeste prateća infrastruktura
Otvaranje dokumenta
Prva stvar koju treba znati o TPdf komponenti jeste da postavljanje Active := True nikada ne podiže izuzetke. Neuspesi učitavanja, pogrešne lozinke, oštećene datoteke: sve se to guta interno i komponenta jednostavno ostaje neaktivna. Morate sami da proverite tu zastavicu nakon dodele, ili ćete nastaviti u petlju stranica sa PageCount koji vraća nulu i pitati se zašto ništa nije ekstrahovano
var
Pdf: TPdf;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := 'report.pdf';
Pdf.Active := True;
if not Pdf.Active then
begin
Writeln('Failed to open: ', Pdf.FileName);
Exit;
end;
Writeln(Pdf.PageCount, ' pages');
// proceed to extraction
finally
Pdf.Free;
end;
end;
Datoteke zaštićene lozinkom prate isti šablon: dodelite vrednost svojstvu Pdf.Password pre nego što postavite Active := True. Ako je lozinka pogrešna, Active ostaje False i ne dobijate izuzetak koji biste mogli da uhvatite. U paketnom alatu koji obrađuje stotine datoteka, to tiho ponašanje je zapravo korisno: akumulirate neuspehe u listu umesto da odmotavate pozivni stek (call stack) za svaki pojedinačni neuspeh
Prolazak kroz stranice i izvlačenje bitmapa
BitmapCount je po stranici, tako da podešavate Pdf.PageNumber pre nego što ga pročitate. Brojevi stranica počinju od 1; podrazumevana vrednost je 0, što znači da nijedna stranica nije učitana. Svojstvo Bitmap[Index] je indeksirano od nule i vraća TBitmap čiji je vlasnik pozivaoc. Morate ga osloboditi. Zanemarite oslobađanje unutar duge petlje na velikom dokumentu i memorija će brzo rasti, jer svaka bitmapa može predstavljati nekoliko megabajta sirovih podataka o pikselima pre bilo kakve kompresije
procedure ExtractAllImages(Pdf: TPdf; const OutputDir: string);
var
Page, Idx: Integer;
Bmp: TBitmap;
OutPath: string;
begin
for Page := 1 to Pdf.PageCount do
begin
Pdf.PageNumber := Page;
for Idx := 0 to Pdf.BitmapCount - 1 do
begin
Bmp := Pdf.Bitmap[Idx];
if not Assigned(Bmp) then
Continue;
try
OutPath := Format('%s\p%d_img%d.bmp', [OutputDir, Page, Idx + 1]);
Bmp.SaveToFile(OutPath);
finally
Bmp.Free;
end;
end;
end;
end;
Provera pomoću Assigned je važna. Mali broj generatora PDF-a upisuje slikovne XObject-e sa nultim dimenzijama piksela ili na drugi način neispravnim podacima; u tim slučajevima komponenta vraća nil umesto prazne bitmape. Tretiranje nil povrata kao greške i zaustavljanje ekstrakcije je pogrešan refleks: preskočite ga, zabeležite stranicu i indeks ako vam je potreban trag revizije i nastavite dalje. Ostatak stranice i dalje može dati ispravne slike
Primetite da spoljna petlja postavlja Pdf.PageNumber u svakoj iteraciji. Ta dodela je ono što učitava stranicu u interno stanje komponente i čini BitmapCount smislenim. Preskočite to i čitaćete broj slika iste stranice više puta. Šablon izgleda redundantno kada ga pišete, ali tako je API dizajniran: stranica je kursor, a ne kolekcija
Izbor izlaznog formata
BMP je bez gubitaka (lossless) i uvek je dostupan bez dodatnih biblioteka, što ga čini dobrim podrazumevanim izborom kada još uvek ne znate šta slika sadrži. Kada je veličina datoteke važna, format piksela vraćenog TBitmap-a vam govori koji je kodek odgovarajući. 32-bitna bitmapa nosi alfa kanal; PNG to čuva bez gubitaka. Velika 24-bitna slika sa neprekidnim tonovima je kandidat za JPEG. Manje slike ili one nacrtane sa ograničenom paletom boja je generalno bolje ostaviti kao BMP nego ih provlačiti kroz JPEG, koji dodaje blokovske artefakte na postavkama niskog kvaliteta, a štedi malo na visokim postavkama
procedure SaveBitmap(Bmp: TBitmap; const FileName: string);
var
Jpg: TJPEGImage;
begin
case UpperCase(ExtractFileExt(FileName)) of
'.JPG', '.JPEG':
begin
Jpg := TJPEGImage.Create;
try
Jpg.Assign(Bmp);
Jpg.CompressionQuality := 85;
Jpg.SaveToFile(FileName);
finally
Jpg.Free;
end;
end;
else
Bmp.SaveToFile(FileName); // BMP: lossless, no extra units
end;
end;
U praksi, izbor formata diktiraju Bmp.PixelFormat i dimenzije. Ako je PixelFormat = pf32bit, potreban vam je format koji prenosi alfa kanal; PNG is očigledan izbor, iako zahteva PNGImage jedinicu u starijim verzijama Delphi-ja. Za 24-bitne slike šire od otprilike 300 piksela, JPEG sa kvalitetom 85 daje trostruko smanjenje veličine u odnosu na BMP bez primetnog gubitka u većini fotografskih sadržaja. Ispod tog praga, BMP je uporediv po veličini i u potpunosti izbegava bilo kakvu odluku o kvalitetu
Šta BitmapCount broji, a šta ne
PDF razlikuje slikovne XObject-e i vektorsku grafiku nacrtanu operatorima putanje. Stranica koja izgleda vizuelno složeno može vratiti BitmapCount jednak nuli ako je svaki element vektor. Skenirane stranice skoro uvek vraćaju tačno jedan: skener upisuje celo skeniranje kao jedan XObject slike cele stranice u rezoluciji na koju je skener bio podešen. Stranice koje mešaju otkucani tekst sa ugrađenim fotografijama vraćaju po jedan unos za svaku fotografiju. Dekorativne linije, osenčene pozadine i okviri tabela obično se uopšte ne pojavljuju u broju bitmapa
Broj takođe ne uključuje inline slike, retko korišćeni PDF konstrukt gde su podaci o slici ugrađeni direktno u tok sadržaja stranice, a ne kao imenovani XObject. To izlazi van okvira onoga što ovaj API prikazuje; one su neuobičajene u stvarnim dokumentima i većina alata za ekstrakciju ih jednostavno ne obrađuje
Jedan detalj koji vredi imati na umu: BitmapCount koji čitate odnosi se na trenutnu stranicu od poslednje dodele svojstva PageNumber. Ako se vaš kod grana ili poziva bilo koju funkciju koja menja PageNumber između brojanja i preuzimanja, možete pročitati manje slika nego što ste alocirali prostora, ili indeksirati van opsega. Držite čitanje broja i petlju Bitmap[] na istoj stranici bez dodirivanja svojstva PageNumber u međuvremenu
Korišćenje TPdfView u aplikaciji sa formom
Memorija i performanse u paketnim poslovima
U celoj arhivi, budžet memorije je glavna stvar na koju treba paziti. Svaki poziv Bitmap[] alocira novi TBitmap na hipu (heap), a na skeniranoj stranici od 300 DPI to je lako 25 MB sirovih podataka o pikselima pre bilo kakvog kodiranja. Ako obrađujete stranice u tesnoj petlji bez oslobađanja između iteracija, radni skup raste linearno sa brojem slika. Ispravan oblik je uvek: preuzmite jednu bitmapu, uradite šta vam treba, oslobodite je, preuzmite sledeću. Ako treba da držite reference na nekoliko bitmapa odjednom radi koraka poređenja, prvo ih prebrojte pomoću BitmapCount i u skladu sa tim alocirajte svoj kontejner, a zatim oslobodite svaku čim završite sa njom, umesto da odlažete čišćenje za kraj dokumenta. Na dokumentu sa 500 skeniranih stranica ta razlika može značiti razliku između 25 MB i 12 GB vršne memorije (peak RSS)
Komponenta TPdfView izlaže ista svojstva BitmapCount i Bitmap[], ali stranica sa koje čita je trenutno prikazana stranica prikaza, a ne TPdf.PageNumber. Dva pokazivača stranica su nezavisna; postavljanje jednog ne pomera drugi. U VCL aplikaciji sa formom sa prikazom uživo, možete pozvati Pdf.PageNumber := N da biste pokrenuli ekstrakciju preko TPdf dok prikaz ostaje na onome što je korisnik poslednje skrolovao. To razdvajanje je namerno i održava stanje prikaza čitača čistim dok se u pozadini odvija ekstrakcija
Svojstva BitmapCount i Bitmap[] prikazana ovde su deo PDFium Component komponente za Delphi i C++Builder