PDF gemmer billeder som førsteklasses objekter inde i sine indholdsstrømme. Når en side refererer til et fotografi, en scanning eller et diagram, ligger pixeldataene i en XObject-ordbog sammen med sidegeometrien. PDFium Component synliggør dette gennem to egenskaber på TPdf: BitmapCount, som returnerer, hvor mange indlejrede bitmaps der er på den aktuelle side, og Bitmap[Index], som afkoder et af dem til en TBitmap, du ejer og skal frigive. Det er hele udtrækningsmodellen. Løkken er fire linjer; det, der kræver dømmekraft, er den omkringliggende logik
Åbning af dokumentet
Det første man skal vide om TPdf er, at Active := True aldrig kaster en undtagelse. Indlæsningsfejl, forkerte adgangskoder, beskadigede filer: alle disse sluges internt, og komponenten forbliver simpelthen inaktiv. Du skal selv tjekke flaget efter tildelingen, ellers vil du fortsætte ind i sideløkken med en PageCount, der returnerer nul, og undre dig over, hvorfor intet blev udtrukket
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;
Adgangskodebeskyttede filer følger det samme mønster: tildel Pdf.Password før du sætter Active := True. Hvis adgangskoden er forkert, forbliver Active som False, og du får ingen undtagelse at fange. I et batchværktøj, der behandler hundredvis af filer, er den tavse adfærd faktisk nyttig: du akkumulerer fejlene i en liste i stedet for at rulle kaldstakken tilbage for hver enkelt
Iteration af sider og udtrækning af bitmaps
BitmapCount er pr. side, så du sætter Pdf.PageNumber før du læser det. Sidenumre er 1-baserede; standarden er 0, hvilket betyder, at ingen side er indlæst. Egenskaben Bitmap[Index] er 0-baseret og returnerer en opkalder-ejet TBitmap. Du skal frigive den. Glemmer du at frigive den inde i en lang løkke over et stort dokument, stiger hukommelsesforbruget hurtigt, fordi hver bitmap kan være flere megabyte af rå pixeldata før nogen komprimering
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;
Vagten for Assigned er vigtig. Et lille antal PDF-generatorer skriver billed-XObjects med nul pixeldimensioner eller andre misdannede data; i disse tilfælde returnerer komponenten nil i stedet for en tom bitmap. At behandle en nil-returværdi som en fejl og stoppe udtrækningen er den forkerte refleks: spring den over, log siden og indekset, hvis du har brug for et revisionsspor, og fortsæt. Resten af siden kan stadig give gyldige billeder
Bemærk, at den ydre løkke sætter Pdf.PageNumber ved hver iteration. Den tildeling er det, der indlæser siden i komponentens interne tilstand og gør BitmapCount meningsfuld. Springer du det over, læser du den samme sides antal gentagne gange. Mønsteret føles overflødigt, når du skriver det, men det er sådan API'et er designet: siden er en markør, ikke en samling
Valg af et outputformat
BMP er tabsfri og altid tilgængelig uden yderligere units, hvilket gør det til en fornuftig standard, når du endnu ikke ved, hvad billedet indeholder. Når filstørrelsen betyder noget, fortæller pixelformatet for den returnerede TBitmap dig, hvilket codec der er passende. En 32-bit bitmap bærer en alfakanal; PNG bevarer det uden tab. Et stort 24-bit billede med kontinuerlige toner er en kandidat til JPEG. Mindre billeder eller dem tegnet med en begrænset palet er generelt bedre at lade forblive som BMP frem for at køre dem gennem JPEG, hvilket tilføjer blokeringsartefakter ved lave kvalitetsindstillinger og sparer lidt ved høje indstillinger
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;
I praksis styres valget af format af Bmp.PixelFormat og dimensionerne. Hvis PixelFormat = pf32bit, har du brug for et format, der bærer alfa; PNG er det åbenlyse valg, selvom det kræver PNGImage-unit'en i ældre Delphi-versioner. For 24-bit billeder, der er bredere end ca. 300 pixels, giver JPEG ved kvalitet 85 en reduktion i størrelse på tre til en i forhold til BMP uden noget mærkbart tab i de fleste fotografiske indholdstyper. Under denne grænse er BMP sammenlignelig i størrelse og undgår enhver kvalitetsbeslutning helt
Hvad BitmapCount tæller og ikke tæller
PDF skelner mellem billed-XObjects og vektorgrafik tegnet med sti-operatorer. En side, der ser visuelt kompleks ud, kan returnere en BitmapCount på nul, hvis hvert element er vektor. Scannede sider returnerer næsten altid præcis en: scanneren skriver hele scanningen som et enkelt fuldsides billed-XObject ved den opløsning, scanneren var indstillet til. Sider, der blander sat tekst med indlejrede fotografier, returnerer en post pr. fotografi. Dekorative linjer, skyggede baggrunde og tabelkanter vises normalt slet ikke i antallet af bitmaps
Antallet inkluderer heller ikke indlejrede billeder (inline images), en sjældent brugt PDF-konstruktion, hvor billeddata er indlejret direkte i sidens indholdsstrøm i stedet for som et navngivet XObject. Disse falder uden for, hvad dette API synliggør; de er ualmindelige nok i rigtige dokumenter til, at de fleste udtrækningsværktøjer simpelthen ikke håndterer dem
En detalje, der er værd at huske: den BitmapCount, du læser, gælder for den aktuelle side på tidspunktet for den sidste tildeling af PageNumber. Hvis din kode forgrener sig eller kalder en funktion, der ændrer PageNumber mellem tælling og hentning, kan du risikere at læse færre billeder, end du har afsat plads til, eller at indeksere forbi slutningen. Hold aflæsningen af antallet og Bitmap[]-løkken på den samme side uden at røre ved PageNumber indimellem
Brug af TPdfView i en formularapplikation
Komponenten TPdfView afslører de samme egenskaber for BitmapCount og Bitmap[], men den side, den læser fra, er visningens aktuelt viste side, ikke TPdf.PageNumber. De to sidepegere er uafhængige; at sætte den ene flytter ikke den anden. I en VCL-formularapplikation med en live fremviser, kan du kalde Pdf.PageNumber := N for at køre udtrækningen gennem TPdf, mens fremviseren forbliver på det, som brugeren sidst rullede til. Den adskillelse er bevidst og holder fremviserens visningstilstand ren, mens en baggrundsudtrækning kører
Hukommelse og ydeevne i batchjob
På tværs af et stort arkiv er hukommelsesbudgettet det vigtigste at holde øje med. Hvert kald til Bitmap[] tildeler en ny TBitmap på heapen, og på en 300 DPI scannet side er det nemt 25 MB af rå pixeldata før nogen kodning. Hvis du behandler sider i en stram løkke uden at frigive hukommelse mellem iterationerne, vokser arbejdssættet lineært med antallet af billeder. Den korrekte fremgangsmåde er altid: hent et bitmap, gør hvad du skal, frigiv det, hent det næste. Hvis du har brug for at holde referencer til flere bitmaps på én gang til et sammenligningstrin, så tæl dem først med BitmapCount og alloker din container derefter, og frigiv dem så én efter én, så snart du er færdig med dem, i stedet for at udskyde det til oprydningen i slutningen af dokumentet. På et dokument med 500 scannede sider kan den forskel betyde forskellen mellem et maksimalt hukommelsesforbrug (RSS) på 25 MB og 12 GB
Egenskaberne BitmapCount og Bitmap[], der er vist her, er en del af PDFium Component til Delphi og C++Builder