PDF lagrar bilder som förstklassiga objekt inuti sina innehållsströmmar (content streams). När en sida refererar till ett fotografi, en skanning eller ett diagram, lever pixeldatan i en XObject-ordbok (XObject dictionary) vid sidan av sidgeometrin. PDFium Component synliggör (surfaces) det genom två egenskaper på TPdf: BitmapCount, som returnerar hur många inbäddade bitmappar som finns på den aktuella sidan, och Bitmap[Index], som avkodar (decodes) en av dem till en TBitmap som du äger och måste frigöra. Det är hela extraheringsmodellen. Loopen är fyra rader; vad som kräver omdöme (judgment) är de omgivande rördragningarna (plumbing)
Att öppna dokumentet
Den första saken att veta om TPdf är att Active := True aldrig utlöser (raises). Laddningsfel, fel lösenord, korrupta filer: alla sväljs internt och komponenten förblir helt enkelt inaktiv. Du måste själv kontrollera flaggan efter tilldelningen, annars fortsätter du in i sid-loopen (page loop) med att PageCount returnerar noll och undrar varför ingenting extraherades
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;
Lösenordsskyddade filer följer samma mönster: tilldela Pdf.Password innan du sätter Active := True. Om lösenordet är fel stannar Active på False och du får inget undantag att fånga. I ett satsverktyg (batch tool) som bearbetar hundratals filer är det tysta beteendet faktiskt användbart: du ackumulerar (accumulate) felen i en lista i stället för att nysta upp anropsstacken (unwinding the call stack) för varje enskilt fel
Att iterera sidor och dra bitmappar
BitmapCount är per sida, så du sätter Pdf.PageNumber innan du läser den. Sidnummer är 1-baserade; standardvärdet är 0, vilket innebär att ingen sida är laddad. Egenskapen Bitmap[Index] är 0-baserad och returnerar en uppringare-ägd (caller-owned) TBitmap. Du måste frigöra den. Försumma (neglect) frigörelsen inuti en lång loop över ett stort dokument, och minnet klättrar snabbt, eftersom varje bitmapp kan vara flera megabyte av rå pixeldata före eventuell 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;
Assigned-skyddet spelar roll. Ett litet antal PDF-generatorer skriver bild-XObjects med nollpixeldimensioner eller annan felformaterad data (malformed data); i de fallen returnerar komponenten nil snarare än en tom bitmapp. Att behandla en nil-retur som ett fel och stoppa extraheringen är fel reflex: hoppa över den, logga sidan och indexet om du behöver granskningsspåret (audit trail), och fortsätt. Resten av sidan kan fortfarande ge giltiga bilder
Lägg märke till att den yttre loopen sätter Pdf.PageNumber i varje iteration. Den tilldelningen är vad som laddar sidan in i komponentens interna tillstånd och gör BitmapCount meningsfull. Hoppa över den och du läser samma sidas antal upprepade gånger. Mönstret känns överflödigt när du skriver det, men det är så API:et är designat: sidan är en markör (cursor), inte en samling
Att välja ett utdataformat
BMP är förlustfritt och alltid tillgängligt utan extra enheter (units), vilket gör det till en sund standard när du ännu inte vet vad bilden innehåller. När filstorleken spelar roll berättar pixelformatet på den returnerade TBitmap vilken kodek (codec) som är lämplig. En 32-bitars bitmapp bär en alfakanal (alpha channel); PNG bevarar den utan förlust. En stor 24-bitars bild med kontinuerlig ton (continuous tone) är en kandidat för JPEG. Mindre bilder eller sådana som är ritade med en begränsad palett (palette) lämnas i allmänhet bättre som BMP än att köras genom JPEG, vilket lägger till blockeringsartefakter (blocking artifacts) vid låga kvalitetsinställningar och sparar lite vid höga
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 praktiken drivs formatvalet av Bmp.PixelFormat och dimensioner. Om PixelFormat = pf32bit behöver du ett format som bär alfa; PNG är det uppenbara valet, även om det kräver PNGImage-enheten (unit) i äldre Delphi-versioner. För 24-bitars bilder bredare än ungefär 300 pixlar ger JPEG med kvalitet 85 en tre-till-ett-storleksminskning över BMP utan märkbar förlust i det mesta fotografiska innehållet. Under den tröskeln är BMP jämförbart i storlek och undviker helt alla kvalitetsbeslut
Vad BitmapCount räknar och inte räknar
PDF skiljer på bild-XObjects och vektorgrafik ritad med banoperatörer (path operators). En sida som ser visuellt komplex ut kan returnera en BitmapCount på noll om varje element är vektor. Skannade sidor returnerar nästan alltid exakt en: skannern skriver hela skanningen som ett enda fullsides bild-XObject med vilken upplösning skannern än var inställd på. Sidor som blandar typsatt (typeset) text med inbäddade fotografier returnerar en post per fotografi. Dekorativa linjer (rule lines), skuggade bakgrunder och tabellramar (table borders) visas vanligtvis inte alls i bitmappsantalet
Antalet inkluderar inte heller inbäddade bilder (inline images), en sällan använd PDF-konstruktion där bilddata bäddas in direkt i sidans innehållsström snarare än som ett namngivet XObject. Dessa faller utanför vad detta API synliggör (surfaces); de är tillräckligt ovanliga i riktiga dokument för att de flesta extraheringsverktyg helt enkelt inte hanterar dem
En detalj värd att komma ihåg: den BitmapCount du läser är för den aktuella sidan enligt den senaste PageNumber-tilldelningen. Om din kod grenar (branches) eller anropar någon funktion som ändrar PageNumber mellan räknandet och hämtningen, kan du läsa in färre bilder än vad du allokerade utrymme för, eller indexera (index) förbi slutet. Håll räkningen (the count read) och Bitmap[]-loopen på samma sida utan att röra PageNumber däremellan
Att använda TPdfView i en formulärapplikation
TPdfView-komponenten exponerar (exposes) samma egenskaper, BitmapCount och Bitmap[], men sidan den läser från är visarens för närvarande visade sida, inte TPdf.PageNumber. De två sidpekarna (page pointers) är oberoende; att ställa in den ena flyttar inte den andra. I en VCL-formulärapplikation med en live-visare (live viewer) kan du anropa Pdf.PageNumber := N för att driva extrahering genom TPdf medan visaren stannar på vad användaren senast skrollade till. Denna separation är avsiktlig och håller visarens visningstillstånd (display state) rent medan en bakgrundsextrahering körs
Minne och prestanda i satsjobb
Över ett stort arkiv är minnesbudgeten det viktigaste (the main thing) att hålla koll på. Varje Bitmap[]-anrop allokerar en ny TBitmap på heapen (the heap), och på en 300 DPI skannad sida är det lätt 25 MB av rå pixeldata före eventuell kodning. Om du bearbetar sidor i en snäv loop (tight loop) utan att frigöra mellan iterationerna, växer arbetsmängden linjärt med antalet bilder. Den korrekta formen (shape) är alltid: hämta en bitmapp, gör vad du behöver, frigör den, hämta nästa. Om du behöver hålla referenser till flera bitmappar samtidigt för ett jämförelsesteg, räkna dem först med BitmapCount och allokera din behållare (container) därefter, och frigör sedan var och en så fort du är klar med den snarare än att skjuta upp det till dokumentets slutstädning. På ett dokument med 500 skannade sidor kan den distinktionen innebära skillnaden mellan 25 MB och 12 GB maximal RSS (peak RSS)
Egenskaperna BitmapCount och Bitmap[] som visas här är en del av PDFium Component för Delphi och C++Builder