Tekninen artikkeli

Kuvien (Extracting Images from PDFs with PDFium Component in Delphi) purkaminen PDF-asiakirjoista Delphissä PDFium Component -komponentilla

PDF tallentaa kuvat ensiluokkaisina objekteina sisältövirtojensa sisällä Kun sivu viittaa valokuvaan, skannaukseen tai kaavioon, pikselidata sijaitsee XObject-sanakirjassa sivun geometrian rinnalla PDFium Component tuo tämän esiin kahden TPdf-ominaisuuden kautta: BitmapCount, joka palauttaa nykyiselle sivulle upotettujen bittikarttojen lukumäärän, ja Bitmap[Index], joka purkaa yhden niistä TBitmap-objektiksi, jonka omistus siirtyy kutsujalle ja joka on vapautettava Siinä koko purkumalli Silmukka on nelirivinen; harkintaa vaatii ympäröivä putkitus

Asiakirjan avaaminen

Ensimmäinen asia, joka TPdf-komponentista kannattaa tietää, on se, ettei Active := True koskaan nosta poikkeusta Latausvirheet, väärät salasanat, vioittuneet tiedostot: kaikki nämä niellään sisäisesti, ja komponentti jää yksinkertaisesti passiiviseksi Lippu on tarkistettava itse sijoituksen jälkeen, tai muuten päädytään sivusilmukkaan, jossa PageCount palauttaa nollan, ja jäädään ihmettelemään, miksi mitään ei purettu

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');
    // jatka purkamiseen
  finally
    Pdf.Free;
  end;
end;

Salasanasuojatut tiedostot toimivat samalla tavalla: aseta Pdf.Password ennen kuin asetat Active := True Jos salasana on väärä, Active jää arvoon False, eikä poikkeusta ole tarttumassa Satojen tiedostojen eräajossa tuo hiljainen käytös on itse asiassa hyödyllinen: virheet kerätään listaan sen sijaan, että kutsupinoa purettaisiin jokaisen kohdalla erikseen

Sivujen läpikäynti ja bittikarttojen nouto

BitmapCount on sivukohtainen, joten Pdf.PageNumber on asetettava ennen sen lukemista Sivunumerointi alkaa ykkösestä; oletusarvo on 0, mikä tarkoittaa, ettei sivua ole ladattu Bitmap[Index]-ominaisuus on nollapohjainen ja palauttaa kutsujan omistukseen siirtyvän TBitmap-objektin Se on vapautettava Jos vapautus unohtuu pitkässä silmukassa suuren asiakirjan yli, muistinkäyttö kasvaa nopeasti, koska yksi bittikartta voi olla useita megatavuja raakaa pikselidataa ennen mitään pakkausta

PDF-kuvien poimintasilmukka Delphissä PDFiumilla, joka tarkistaa hiljaisen Active-lipun, asettaa PageNumberin sivua kohden, lukee BitmapCountin, ohittaa nil-bittikartat ja vapauttaa jokaisen TBitmapin tallennuksen jälkeen
Silmukka lukee määrän vasta, kun sivukohdistin on siirtynyt, ohittaa nil-bittikartat viallisista XObjecteista ja vapauttaa jokaisen kutsujan omistaman TBitmap:n ennen seuraavan noutamista
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-tarkistuksella on merkitystä Pieni osa PDF-generaattoreista kirjoittaa kuva-XObjecteja, joiden pikselimitat ovat nolla tai joiden data on muuten virheellinen; näissä tapauksissa komponentti palauttaa nil-arvon tyhjän bittikartan sijaan nil-paluuarvon käsitteleminen virheenä ja purkamisen pysäyttäminen on väärä refleksi: ohita se, kirjaa sivu ja indeksi lokiin, jos tarvitset jäljitysketjun, ja jatka Loppu sivusta voi silti tuottaa kelvollisia kuvia

Huomaa, että ulompi silmukka asettaa Pdf.PageNumber-arvon jokaisella kierroksella Juuri tuo sijoitus lataa sivun komponentin sisäiseen tilaan ja tekee BitmapCount-arvosta merkityksellisen Jätä se väliin, ja lukemasi määrä koskee samaa sivua toistuvasti Kuvio tuntuu kirjoitettaessa toistolta, mutta se on rajapinnan suunnittelutapa: sivu on kursori, ei kokoelma

Tulostusmuodon valinta

BMP on häviötön ja aina käytettävissä ilman lisäyksiköitä, mikä tekee siitä järkevän oletuksen silloin, kun kuvan sisältöä ei vielä tunneta Kun tiedostokoko on tärkeä, palautetun TBitmap-objektin pikselimuoto kertoo, mikä koodekki sopii 32-bittinen bittikartta sisältää alfakanavan; PNG säilyttää sen häviöttömästi Suuri 24-bittinen kuva jatkuvalla sävyllä on JPEG-ehdokas Pienemmät kuvat tai rajoitetulla paletilla piirretyt kannattaa yleensä jättää BMP-muotoon JPEG-muunnoksen sijaan, sillä se lisää lohkoartefakteja alhaisilla laatuasetuksilla eikä säästä juuri mitään korkeilla

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: häviötön, ei lisäyksiköitä
  end;
end;

Käytännössä muodon valinta perustuu Bmp.PixelFormat-arvoon ja mittoihin Jos PixelFormat = pf32bit, tarvitaan muoto, joka kantaa alfakanavaa; PNG on ilmeinen valinta, vaikka se vaatii vanhemmissa Delphi-versioissa PNGImage-yksikön Noin 300 pikseliä leveämmillä 24-bittisillä kuvilla JPEG laadulla 85 antaa noin kolmasosan koon BMP:hen verrattuna ilman havaittavaa laadun heikkenemistä useimmissa valokuvamaisissa sisällöissä Tuon kynnyksen alapuolella BMP on kooltaan verrattavissa ja välttää laatupäätöksen kokonaan

Mitä BitmapCount laskee ja mitä ei

PDF erottaa kuva-XObjectit ja polkuoperaattoreilla piirretyt vektorigrafiikat toisistaan Visuaalisesti monimutkaiselta näyttävä sivu voi palauttaa BitmapCount-arvon nolla, jos jokainen elementti on vektorimuotoinen Skannatut sivut palauttavat lähes aina tasan yhden: skanneri kirjoittaa koko skannauksen yhtenä koko sivun kuva-XObjectina sillä resoluutiolla, johon skanneri oli asetettu Sivut, joissa ladottu teksti sekoittuu upotettuihin valokuviin, palauttavat yhden merkinnän kutakin valokuvaa kohden Koristeelliset viivat, sävytetyt taustat ja taulukoiden reunaviivat eivät yleensä näy bittikarttalaskennassa lainkaan

Mitä BitmapCount kattaa PDF-sivulla PDFiumissa, laskeva kuva-XObjectit kuten skannaukset ja valokuvat, kun taas vektoriteksti, viivaviivat, varjostetut täytöt ja upotetut kuvat jäävät laskennan ulkopuolelle
Skannattu sivu palauttaa täsmälleen yhden koko sivun kattavan kuva-XObjectin, kun taas puhtaasti vektoroitu sivu voi näyttää vilkkaalta ja silti palauttaa nollan määrän

Laskenta ei myöskään sisällä upotettuja kuvia (inline images), harvoin käytettyä PDF-rakennetta, jossa kuvadata upotetaan suoraan sivun sisältövirtaan nimetyn XObjectin sijaan Ne jäävät tämän rajapinnan ulottumattomiin; ne ovat todellisissa asiakirjoissa niin harvinaisia, ettei useimpia purkutyökaluja ole rakennettu käsittelemään niitä

Yksi asia kannattaa pitää mielessä: luettu BitmapCount koskee nykyistä sivua sellaisena kuin se oli viimeisimmän PageNumber-sijoituksen hetkellä Jos koodi haarautuu tai kutsuu jotain funktiota, joka muuttaa PageNumber-arvoa laskennan ja noudon välissä, saatetaan lukea vähemmän kuvia kuin mille varattiin tilaa, tai indeksoida loppupisteen ohi Pidä laskennan luku ja Bitmap[]-silmukka samalla sivulla koskematta PageNumber-arvoon niiden välissä

TPdfView-komponentin käyttö lomakesovelluksessa

TPdfView-komponentti tarjoaa samat BitmapCount- ja Bitmap[]-ominaisuudet, mutta sen sivu, jolta se lukee, on näkymän parhaillaan näyttämä sivu, ei TPdf.PageNumber Kaksi sivuosoitinta ovat toisistaan riippumattomia; toisen asettaminen ei siirrä toista VCL-lomakesovelluksessa, jossa on elävä katseluohjelma, voit kutsua Pdf.PageNumber := N ohjataksesi purkamista TPdf:n kautta, kun taas katseluohjelma pysyy sivulla, jonne käyttäjä viimeksi vieritti Tuo erotus on tarkoituksellinen ja pitää katseluohjelman näyttötilan siistinä taustalla käynnissä olevan purkamisen aikana

Muisti ja suorituskyky eräajoissa

Laajan arkiston yli muistibudjetti on tärkein tarkkailtava asia Jokainen Bitmap[]-kutsu varaa uuden TBitmap-objektin keosta, ja 300 DPI:n resoluutiolla skannatulla sivulla se on helposti 25 Mt raakaa pikselidataa ennen mitään koodausta Jos sivuja käsitellään tiiviissä silmukassa vapauttamatta iteraatioiden välissä, työskentelyjoukko kasvaa lineaarisesti kuvien määrän mukana Oikea kaava on aina: hae yksi bittikartta, tee tarvittava, vapauta se, hae seuraava Jos tarvitset viittauksia useisiin bittikarttoihin samanaikaisesti vertailuvaihetta varten, laske ne ensin BitmapCount-arvolla ja varaa säiliö sen mukaisesti, ja vapauta sitten jokainen heti, kun se ei enää ole tarpeen, sen sijaan että siirtäisit siivouksen asiakirjan loppuun 500 skannatun sivun asiakirjalla tuo ero voi tarkoittaa 25 Mt:n ja 12 Gt:n huippumuistinkäytön välistä eroa

Tässä esitellyt BitmapCount- ja Bitmap[]-ominaisuudet kuuluvat Delphille ja C++Builderille tarkoitettuun PDFium Component -kirjastoon