Tehnični članak

Ekstrakcija slik iz PDF dokumentov s PDFium Component v Delphiju

PDF shranjuje slike kot objekte prvega razreda znotraj svojih vsebinskih tokov. Ko se stran sklicuje na fotografijo, skenirano sliko ali diagram, slikovni podatki živijo v slovarju XObject poleg geometrije strani. PDFium Component to izpostavlja prek dveh lastnosti v TPdf: BitmapCount, ki vrne število vgrajenih bitnih slik na trenutni strani, in Bitmap[Index], ki eno izmed njih dekodira v objekt TBitmap, ki je v vaši lasti in ga morate sami sprostiti. To je celoten model ekstrakcije. Zanka obsega le štiri vrstice, več premisleka pa zahteva okoljska koda

Odpiranje dokumenta

Prva stvar, ki jo morate vedeti o TPdf, je, da nastavitev Active := True nikoli ne sproži izjeme. Napake pri nalaganju, napačna gesla, poškodovane datoteke: vse to se ujame interno, komponenta pa preprosto ostane neaktivna. Po dodelitvi morate sami preveriti to zastavico, sicer boste nadaljevali v zanko strani, kjer bo PageCount vrnil nič, vi pa se boste spraševali, zakaj se nič ni izvleklo

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;

Z geslom zaščitene datoteke sledijo enakemu vzorcu: določite Pdf.Password pred nastavitvijo Active := True. Če je geslo napačno, ostane lastnost Active na False in ne prejmete nobene izjeme, ki bi jo lahko ulovili. Pri paketnem orodju, ki obdeluje stotine datotek, je to tiho delovanje dejansko uporabno: napake preprosto zberete na seznamu, namesto da bi za vsako datoteko posebej prekinjali izvajanje

Pomikanje po straneh in pridobivanje bitnih slik

Vrednost BitmapCount velja za posamezno stran, zato morate pred njenim branjem nastaviti Pdf.PageNumber. Številke strani se začnejo z 1; privzeta vrednost je 0, kar pomeni, da ni naložena nobena stran. Lastnost Bitmap[Index] ima indekse z začetkom pri 0 in vrne objekt TBitmap v vaši lasti. To bitno sliko morate sami sprostiti. Če sprostitev opustite znotraj dolge zanke čez obsežen dokument, bo poraba pomnilnika hitro narasla, saj lahko vsaka bitna slika pred stiskanjem predstavlja več megabajtov surovih slikovnih podatkov

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;

Varovanje z Assigned je pomembno. Majhno število generatorjev PDF zapiše slikovne XObjects z ničelnimi dimenzijami slikovnih pik ali drugače poškodovanimi podatki; v teh primerih komponenta vrne nil namesto prazne bitne slike. Obravnava vrnitve nil kot napake in ustavitev ekstrakcije je napačen refleks: preskočite jo, po potrebi zabeležite stran in indeks ter nadaljujte. Preostanek strani morda še vedno vrne veljavne slike

Upoštevajte, da zunanja zanka nastavi Pdf.PageNumber pri vsaki ponovitvi. Ta dodelitev je tista, ki naloži stran v notranje stanje komponente in naredi BitmapCount smiseln. Preskočite jo in pri vsaki ponovitvi berete število iste strani. Vzorec se pri pisanju zdi odveč, toda tako je zasnovan API: stran je kazalec, ne zbirka

Izbira izhodne oblike

BMP je brez izgub in vedno na voljo brez dodatnih enot, kar ga naredi za dobro privzeto vrednost, ko še ne veste, kaj slika vsebuje. Ko je velikost datoteke pomembna, format slikovnih pik vrnjene TBitmap pove, kateri kodek je primeren. 32-bitna bitna slika nosi alfa kanal; PNG ga ohranja brez izgub. Velika 24-bitna slika z zveznimi toni je kandidat za JPEG. Manjše slike ali tiste, narisane z omejeno paleto, so na splošno bolje puščene kot BMP kot pa obdelane z JPEG, ki pri nizkih nastavitvah kakovosti doda blokovne artefakte in pri visokih malo prihrani

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;

V praksi je izbira oblike odvisna od Bmp.PixelFormat in dimenzij. Če je PixelFormat = pf32bit, potrebujete obliko, ki nosi alfa; PNG je očitna izbira, čeprav zahteva enoto PNGImage v starejših različicah Delphija. Za 24-bitne slike, širše od približno 300 slikovnih pik, JPEG pri kakovosti 85 zagotovi trikratno zmanjšanje velikosti v primerjavi z BMP brez zaznavnih izgub pri večini fotografske vsebine. Pod to mejo je BMP primerljive velikosti in se izogne kakršni koli odločitvi o kakovosti

Kaj BitmapCount šteje in česa ne

PDF razlikuje med slikovnimi XObjects in vektorsko grafiko, narisano z operaterji poti. Stran, ki je videti vizualno kompleksna, morda vrne BitmapCount nič, če je vsak element vektorski. Skenirane strani skoraj vedno vrnejo natanko eno: optični bralnik zapiše celoten posnet kot eno samo sliko XObject v celotni velikosti strani pri kakršni koli ločljivosti je bil nastavljen. Strani, ki mešajo stavljeno besedilo z vgrajenimi fotografijami, vrnejo en vnos na fotografijo. Okrasne ločilne črte, senčena ozadja in obrobe tabel se v štetju bitnih slik praviloma sploh ne pojavijo

Število prav tako ne vključuje vgrajenih slik, redko uporabljenega konstrukta PDF, pri katerem so slikovni podatki vgrajeni neposredno v vsebinski tok strani in ne kot poimenovan XObject. Ti so zunaj tega, kar ta API izpostavlja; v resničnih dokumentih so dovolj redki, da jih večina orodij za ekstrakcijo preprosto ne obravnava

Ena podrobnost, ki jo je vredno imeti v mislih: vrednost BitmapCount, ki jo berete, velja za trenutno stran glede na zadnjo dodelitev PageNumber. Če vaša koda razveja ali kliče katero koli funkcijo, ki med štetjem in pridobivanjem spremeni PageNumber, morda preberete manj slik, kot ste rezervirali prostora, ali pa indeksirate mimo konca. Ohranite branje števila in zanko Bitmap[] na isti strani, ne da bi vmesno posegali v PageNumber

Uporaba TPdfView v aplikaciji obrazca

Komponenta TPdfView izpostavlja iste lastnosti BitmapCount in Bitmap[], toda stran, iz katere bere, je stran, ki je trenutno prikazana v pogledu, ne TPdf.PageNumber. Oba kazalca strani sta neodvisna; nastavitev enega ne premakne drugega. V aplikaciji VCL z živim pregledovalnikom lahko pokličete Pdf.PageNumber := N, da poganjate ekstrakcijo prek TPdf, medtem ko pregledovalnik ostane na tisti strani, ki jo je uporabnik nazadnje prelistal. Ta ločitev je namerna in ohranja stanje prikaza pregledovalnika čisto, medtem ko teče ekstrakcija v ozadju

Pomnilnik in zmogljivost v paketnih opravilih

Pri veliki arhivi je proračun pomnilnika glavna stvar, na katero je treba paziti. Vsak klic Bitmap[] dodeli nov TBitmap na kup, in na skeniranem listu pri 300 DPI je to zlahka 25 MB surovih slikovnih podatkov pred kakršnim koli kodiranjem. Če obdelujete strani v tesni zanki brez sproščanja med ponovitvami, delovna množica linearno narašča s številom slik. Pravilna oblika je vedno: pridobite eno bitno sliko, naredite kar potrebujete, jo sprostite, pridobite naslednjo. Če morate hkrati držati reference na več bitnih slik za korak primerjave, jih najprej preštejte z BitmapCount in ustrezno dodelite vsebnik, nato pa vsako sprostite takoj, ko ste z njo končali, namesto da odložite čiščenje na konec dokumenta. Pri dokumentu s 500 skeniranimi stranmi ta razlika pomeni razliko med 25 MB in 12 GB konice RSS

Lastnosti BitmapCount in Bitmap[], prikazane tukaj, so del komponente PDFium Component za Delphi in C++Builder