PDF saugo paveikslėlius kaip pirmos klasės objektus (first-class objects) savo turinio srautuose. Kai puslapyje nurodoma nuotrauka, skenavimas ar diagrama, pikselių duomenys yra XObject žodyne, kartu su puslapio geometrija. PDFium komponentas tai pateikia per dvi TPdf savybes: BitmapCount, kuri grąžina, kiek įterptų taškinės grafikos paveikslėlių (bitmaps) yra dabartiniame puslapyje, ir Bitmap[Index], kuri iškoduoją vieną iš jų į jūsų valdomą TBitmap objektą, kurį turite atlaisvinti (free). Tai yra visas išgavimo modelis. Ciklas užima keturias eilutes; sprendimų priėmimo reikalauja tik supanti infrastruktūra (plumbing)
Dokumento atidarymas
Pirmas dalykas, kurį reikia žinoti apie TPdf, yra tai, kad Active := True niekada nesukelia išimties (never raises). Įkėlimo klaidos, neteisingi slaptažodžiai, sugadinti failai: visos šios klaidos yra nuryjamos viduje, ir komponentas tiesiog lieka neaktyvus. Jūs patys turite patikrinti vėliavėlę po priskyrimo, kitaip pereisite prie puslapių ciklo, kur PageCount grąžins nulį, ir stebėsitės, kodėl niekas nebuvo išgauta
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;
Slaptažodžiu apsaugoti failai seka tuo pačiu modeliu: priskirkite Pdf.Password prieš nustatydami Active := True. Jei slaptažodis neteisingas, Active lieka False ir negaunate jokios išimties (exception), kurią galėtumėte pagauti. Masinio apdorojimo įrankyje, apdorojančiame šimtus failų, toks tylus elgesys iš tikrųjų yra naudingas: jūs kaupiate klaidas sąraše, o ne išvyniojate iškvietimų dėklą (call stack) kiekvienai iš jų
Puslapių iteracija ir paveikslėlių traukimas
BitmapCount priklauso kiekvienam puslapiui (per-page), todėl prieš jį nuskaitant reikia nustatyti Pdf.PageNumber. Puslapių numeracija prasideda nuo 1; numatytoji reikšmė yra 0, o tai reiškia, kad joks puslapis neįkeltas. Bitmap[Index] savybė prasideda nuo 0 ir grąžina iškvietėjui priklausantį TBitmap. Jūs privalote jį atlaisvinti (free). Pamiršus jį atlaisvinti ilgame cikle, apdorojančiame didelį dokumentą, atminties sunaudojimas greitai išaugs, nes kiekvienas paveikslėlis (bitmap) gali užimti kelis megabaitus neapdorotų (raw) pikselių duomenų prieš bet kokį suspaudimą
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;
Assigned patikrinimas yra svarbus. Nedidelis skaičius PDF generatorių įrašo vaizdo XObject objektus su nuliniais pikselių matmenimis arba su kitaip sugadintais duomenimis; tokiais atvejais komponentas grąžina nil, o ne tuščią paveikslėlį. Traktuoti nil grąžinimą kaip klaidą ir sustabdyti išgavimą yra neteisingas refleksas: praleiskite jį, užregistruokite puslapį ir indeksą, jei jums reikia audito sekos, ir tęskite. Likusioje puslapio dalyje vis dar gali būti tinkamų paveikslėlių
Atkreipkite dėmesį, kad išorinis ciklas nustato Pdf.PageNumber kiekvienoje iteracijoje. Būtent šis priskyrimas įkelia puslapį į komponento vidinę būseną ir paverčia BitmapCount prasmingu. Praleiskite tai ir jūs skaitysite to paties puslapio skaičių vėl ir vėl. Šis modelis atrodo perteklinis jį rašant, tačiau taip sukurta API: puslapis yra žymeklis (cursor), o ne kolekcija
Išvesties formato pasirinkimas
BMP yra neprarandantis kokybės (lossless) ir visada prieinamas be papildomų modulių (units), todėl jis yra patikimas numatytasis pasirinkimas, kai dar nežinote, kas yra paveikslėlyje. Kai failo dydis yra svarbus, grąžinto TBitmap pikselių formatas (pixel format) nurodo, koks kodekas yra tinkamas. 32 bitų paveikslėlis turi alfa kanalą; PNG jį išsaugo be nuostolių. Didelis 24 bitų paveikslėlis su ištisiniais tonais (continuous tone) yra kandidatas į JPEG. Mažesnius paveikslėlius arba tuos, kurie nupiešti naudojant ribotą paletę, paprastai geriau palikti kaip BMP, nei konvertuoti į JPEG, kuris prideda blokavimo artefaktų esant žemiems kokybės nustatymams ir mažai sutaupo vietos esant aukštiems
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;
Praktikoje formato pasirinkimą lemia Bmp.PixelFormat ir matmenys. Jei PixelFormat = pf32bit, jums reikia formato, palaikančio alfa kanalą; PNG yra akivaizdus pasirinkimas, nors senesnėse Delphi versijose tam reikia PNGImage modulio. 24 bitų paveikslėliams, platesniems nei maždaug 300 pikselių, JPEG su 85 kokybe sumažina dydį tris kartus lyginant su BMP, neprarandant pastebimos kokybės daugumoje fotografinio turinio. Žemiau šios ribos BMP yra panašaus dydžio ir leidžia visiškai išvengti bet kokių sprendimų dėl kokybės
Ką BitmapCount suskaičiuoja ir ko ne
PDF atskiria vaizdo XObject objektus ir vektorinę grafiką, nupieštą naudojant kelio (path) operatorius. Puslapis, kuris vizualiai atrodo sudėtingas, gali grąžinti BitmapCount lygų nuliui, jei kiekvienas elementas yra vektorinis. Skenuoti puslapiai beveik visada grąžina tiksliai vieną: skeneris įrašo visą skenavimą kaip vieną viso puslapio vaizdo XObject objektą bet kokia skiriamąja geba (resolution), kuria skeneris buvo nustatytas. Puslapiai, kuriuose spausdintas tekstas sumaišytas su įterptomis nuotraukomis, grąžina po vieną įrašą kiekvienai nuotraukai. Dekoratyvinės linijos, šešėliuoti fonai ir lentelių rėmeliai paprastai visiškai neatsispindi paveikslėlių skaičiuje
Skaičius taip pat neapima įterptųjų paveikslėlių (inline images) – retai naudojamos PDF konstrukcijos, kur vaizdo duomenys įterpiami tiesiogiai į puslapio turinio srautą, o ne kaip pavadintas XObject objektas. Jie nepatenka į tai, ką ši API pateikia; jie yra pakankamai reti realiuose dokumentuose, todėl dauguma išgavimo įrankių jų tiesiog neapdoroja
Viena detalė, kurią verta atsiminti: BitmapCount, kurį nuskaitote, skirtas dabartiniam puslapiui pagal paskutinį PageNumber priskyrimimą. Jei jūsų kodas šakojasi (branches) arba iškviečia kokią nors funkciją, kuri pakeičia PageNumber tarp skaičiavimo ir paėmimo (fetching), galite nuskaityti mažiau paveikslėlių nei paskyrėte vietos, arba indeksas gali išeiti už ribų. Išlaikykite skaičiaus nuskaitymą ir Bitmap[] ciklą tame pačiame puslapyje neliesdami PageNumber tarp jų
TPdfView naudojimas formos programoje
TPdfView komponentas atveria (exposes) tas pačias BitmapCount ir Bitmap[] savybes, tačiau puslapis, iš kurio jis skaito, yra šiuo metu rodininyje rodomas puslapis, o ne TPdf.PageNumber. Šios dvi puslapių nuorodos (pointers) yra nepriklausomos; nustačius vieną, kita nepaslenkama. VCL formos programoje su tiesiogine peržiūros programa (live viewer) galite iškviesti Pdf.PageNumber := N, kad nukreiptumėte išgavimą per TPdf, kol peržiūros programa lieka ten, kur vartotojas paskutinį kartą slinko. Šis atskyrimas yra apgalvotas ir išlaiko peržiūros programos rodymo būseną švarią, kol fone vyksta išgavimas
Atmintis ir našumas masiniuose darbuose
Apdorojant didelį archyvą, atminties biudžetas yra pagrindinis dalykas, kurį reikia stebėti. Kiekvienas Bitmap[] iškvietimas išskiria (allocates) naują TBitmap atminties krūvoje (heap), ir 300 DPI nuskenuotame puslapyje tai lengvai gali sudaryti 25 MB neapdorotų pikselių duomenų prieš bet kokį kodavimą. Jei apdorojate puslapius griežtame cikle neatlaisvindami atminties tarp iteracijų, darbinis rinkinys (working set) auga tiesiškai su paveikslėlių skaičiumi. Teisingas modelis visada yra toks: paimkite vieną paveikslėlį, padarykite tai, ko jums reikia, atlaisvinkite jį, paimkite kitą. Jei palyginimo žingsniui jums reikia išlaikyti nuorodas į kelis paveikslėlius vienu metu, pirmiausia suskaičiuokite juos su BitmapCount ir atitinkamai paskirstykite vietą savo konteineriui, o tada atlaisvinkite kiekvieną, kai tik baigsite su juo, užuot atidėję tai iki dokumento pabaigos valymo. Dokumente su 500 skenuotų puslapių šis skirtumas gali reikšti skirtumą tarp 25 MB ir 12 GB maksimalaus RSS (peak RSS)
Čia parodytos BitmapCount ir Bitmap[] savybės yra PDFium Component, skirto Delphi ir C++Builder, dalis