A PDF a képeket első osztályú objektumokként (first-class objects) tárolja a tartalom-adatfolyamain (content streams) belül. Amikor egy oldal hivatkozik egy fényképre, egy szkennelésre vagy egy diagramra, a pixeladatok (pixel data) egy XObject szótárban élnek az oldalgeometria mellett. A PDFium Component ezt két tulajdonságon keresztül hozza felszínre a TPdf-en: a BitmapCount, amely visszaadja, hogy hány beágyazott bittérkép (bitmaps) található az aktuális oldalon, és a Bitmap[Index], amely dekódolja az egyiket egy olyan TBitmap objektummá, amelyet ön birtokol, és amelyet fel kell szabadítania (must free). Ez a teljes kinyerési modell (extraction model). A ciklus négy sor; ami megítélést igényel, az a környező vízvezeték-szerelés (surrounding plumbing)
A dokumentum megnyitása
Az első dolog, amit a TPdf-ről tudni kell, hogy az Active := True soha nem dob kivételt (raises). Betöltési hibák (load failures), rossz jelszavak, sérült fájlok: mindet belsőleg nyeli el, és a komponens egyszerűen inaktív marad. Önnek magának kell ellenőriznie a jelzőt (flag) a hozzárendelés után, különben úgy lép be az oldalciklusba, hogy a PageCount nullát ad vissza, és azon fog csodálkozni, hogy miért nem lett kinyerve semmi
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;
A jelszóval védett fájlok ugyanezt a mintát követik: rendelje hozzá a Pdf.Password-öt az Active := True beállítása előtt. Ha a jelszó hibás, az Active False marad, és nem kap elkapható kivételt. Egy több száz fájlt feldolgozó kötegelt eszköz (batch tool) esetében ez a csendes viselkedés (silent behavior) valójában hasznos: összegyűjti a hibákat egy listában, ahelyett, hogy minden egyes hibánál visszagöngyölítené a hívási vermet (unwinding the call stack)
Végighaladás az oldalakon és a bittérképek kihúzása
A BitmapCount oldalankénti (per-page), így a beolvasása előtt beállítja a Pdf.PageNumber-t. Az oldalszámok 1-alapúak (1-based); az alapértelmezett érték a 0, ami azt jelenti, hogy nincs betöltve oldal. A Bitmap[Index] tulajdonság 0-alapú (0-based), és egy hívó által birtokolt (caller-owned) TBitmap-et ad vissza. Fel kell szabadítania (must free it). Ha elhanyagolja a felszabadítást (free) egy hosszú ciklusban, amely egy nagy dokumentumon halad végig, a memória gyorsan az egekbe szökik, mert minden egyes bittérkép több megabájtnyi nyers pixeladat (raw pixel data) lehet, mielőtt bármilyen tömörítésre (compression) sor kerülne
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;
Az Assigned őr (guard) számít. A PDF generátorok kis hányada nulla pixelméretű vagy más módon hibás adatokkal rendelkező kép XObjecteket ír; ezekben az esetekben a komponens nil értéket ad vissza egy üres bittérkép (empty bitmap) helyett. A nil visszatérési értéket hibaként kezelni és a kinyerést leállítani rossz reflex: hagyja ki, naplózza (log) az oldalt és az indexet, ha szüksége van az audit nyomvonalra (audit trail), és folytassa. Az oldal többi része még adhat (yield) érvényes képeket
Vegye észre, hogy a külső ciklus minden iterációban (iteration) beállítja a Pdf.PageNumber-t. Ez a hozzárendelés (assignment) az, ami betölti az oldalt a komponens belső állapotába (internal state), és értelmet ad (makes meaningful) a BitmapCount-nak. Ha kihagyja, ugyanannak az oldalnak a számát (count) fogja ismétlődően (repeatedly) beolvasni. A minta redundánsnak (redundant) tűnik, amikor megírja, de az API-t így tervezték: az oldal egy kurzor, nem pedig egy gyűjtemény (collection)
Egy kimeneti formátum kiválasztása
A BMP veszteségmentes (lossless) és mindig elérhető (available) további egységek (units) nélkül, ami egy megalapozott alapértelmezéssé (sound default) teszi, amikor még nem tudja, mit tartalmaz a kép. Amikor a fájlméret (file size) számít, a visszaadott TBitmap pixelformátuma (pixel format) megmondja, hogy melyik kodek a megfelelő. Egy 32-bites bittérkép egy alfa csatornát (alpha channel) hordoz; a PNG ezt veszteség nélkül megőrzi. Egy nagy, 24-bites folyamatos tónusú (continuous tone) kép jó jelölt (candidate) a JPEG-hez. A kisebb képeket vagy a korlátozott palettával rajzoltakat általában jobb BMP-ként hagyni, mint átfuttatni JPEG-en, amely blokkoló műtermékeket (blocking artifacts) ad hozzá alacsony minőségi beállításoknál, magasan pedig keveset spórol
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;
A gyakorlatban a formátumválasztást a Bmp.PixelFormat és a méretek vezérlik. Ha a PixelFormat = pf32bit, akkor egy olyan formátumra van szüksége, amely hordozza az alfát (alpha); a PNG a nyilvánvaló (obvious) választás, bár a régebbi Delphi verziókban megköveteli a PNGImage egységet (unit). A nagyjából 300 pixelnél szélesebb 24-bites képek esetében a JPEG 85-ös minőségen három az egyhez méretcsökkenést eredményez a BMP-hez képest, a legtöbb fényképes tartalomban észlelhető veszteség nélkül (no perceptible loss). Ez alatt a küszöb (threshold) alatt a BMP mérete összemérhető (comparable), és teljes mértékben elkerül bármilyen minőségi döntést (quality decision)
Mit számol és mit nem számol a BitmapCount
A PDF különbséget tesz a kép XObject-ek és az útvonaloperátorokkal (path operators) megrajzolt vektorgrafikák között. Egy vizuálisan összetettnek tűnő oldal is nulla BitmapCount-ot adhat vissza, ha minden elem vektoros. A beszkennelt (scanned) oldalak szinte mindig pontosan egyet adnak vissza: a szkenner a teljes beolvasást egyetlen, teljes oldalas kép XObject-ként írja fel, amilyen felbontásra (resolution) csak beállították a szkennert. A szedett (typeset) szöveget beágyazott (embedded) fényképekkel keverő oldalak fényképenként egy bejegyzést adnak vissza. A dekoratív vonalak (rule lines), árnyékolt hátterek és táblázatszegélyek (table borders) általában egyáltalán nem jelennek meg a bittérkép-számlálásban
A darabszám (count) nem tartalmazza a soron belüli (inline) képeket sem, ami egy ritkán használt PDF-konstrukció, ahol a képadatok (image data) közvetlenül az oldaltartalom-adatfolyamba (page content stream) ágyazódnak be egy elnevezett XObject helyett. Ezek kívül esnek azon, amit ez az API a felszínre hoz; a valós dokumentumokban elég ritkák ahhoz, hogy a legtöbb kinyerőeszköz egyszerűen ne is foglalkozzon velük
Egy részlet (detail), amit érdemes szem előtt tartani (keeping in mind): a BitmapCount, amit beolvas, az aktuális oldalra vonatkozik (current page) az utolsó PageNumber hozzárendelés szerint. Ha a kódja elágazik (branches), vagy bármilyen olyan függvényt meghív, amely megváltoztatja a PageNumber-t a számlálás és a lekérés között, akkor előfordulhat, hogy kevesebb képet olvas be, mint amennyinek helyet foglalt (allocated space for), vagy túlindexel a végén (index past the end). Tartsa a szám (count) beolvasását és a Bitmap[] ciklust ugyanazon az oldalon (same page), anélkül, hogy közben hozzáérne a PageNumber-höz
A TPdfView használata egy űrlapos alkalmazásban
A TPdfView komponens (component) ugyanezeket a BitmapCount és Bitmap[] tulajdonságokat teszi közzé, de az oldal, amiről beolvas (reads from), az a nézet jelenleg megjelenített (currently displayed) oldala, nem pedig a TPdf.PageNumber. A két oldalmutató (page pointers) független; az egyik beállítása nem mozgatja a másikat. Egy élő megjelenítővel (live viewer) rendelkező VCL űrlapos alkalmazásban (form application) meghívhatja a Pdf.PageNumber := N parancsot a kinyerés vezérléséhez (drive extraction) a TPdf-en keresztül, miközben a megjelenítő azon az oldalon marad, ahová a felhasználó utoljára görgetett (scrolled). Ez a szétválasztás szándékos, és tisztán (clean) tartja a megjelenítő kijelzési állapotát (display state), amíg egy háttérbeli kinyerés (background extraction) fut
Memória és teljesítmény a kötegelt feladatokban
Egy nagy archívumban a memóriaköltségvetés (memory budget) a fő dolog, amire figyelni kell. Minden egyes Bitmap[] hívás egy új TBitmap-et foglal le a halmon (heap), és egy 300 DPI-s szkennelt oldalon ez könnyen 25 MB nyers pixeladatot jelenthet még bármilyen kódolás előtt. Ha szoros (tight) ciklusban dolgozza fel az oldalakat anélkül, hogy felszabadítaná őket az iterációk között, a munkahalmaz (working set) lineárisan növekszik a képek számával. A helyes forma (shape) mindig ez: töltsön le egy bittérképet, csinálja meg, amit kell, szabadítsa fel (free), majd töltse le a következőt (fetch the next). Ha egy összehasonlító (comparison) lépéshez egyszerre több bittérképre vonatkozó hivatkozást (references) kell tartania, akkor először számolja meg őket a BitmapCount-tal, és annak megfelelően foglaljon helyet a tárolójának (container), majd amint befejezte velük a munkát, egyenként szabadítsa fel őket, ahelyett, hogy elhalasztaná (deferring) azt a dokumentumvégi (end-of-document) takarításra. Egy 500 szkennelt oldalt tartalmazó dokumentumon ez a különbség jelentheti a különbséget a 25 MB-os és a 12 GB-os csúcs RSS (peak RSS) között
Az itt bemutatott BitmapCount és Bitmap[] tulajdonságok a Delphihez és C++Builderhez készült PDFium Component részét képezik