Artykuł techniczny

Wyodrębnij obrazy z dokumentów PDF w Delphi za pomocą komponentu PDFium

Strona PDF to lista wyświetlania obiektów, a podczas wyodrębniania obrazów iterujesz po tej liście, aby znaleźć obiekty namalowane jako mapy bitowe. Komponent PDFium udostępnia tę listę poprzez klasę TPdf, pozwalając na zapytanie, ile obiektów znajduje się na stronie, odfiltrowanie obrazów, odczytanie ich oryginalnych wymiarów w pikselach i wyciągnięcie ich danych. Możesz poprosić o obraz jako gotową do użycia mapę bitową Delphi TBitmap lub jako surowe bajty dokładnie w takiej postaci, w jakiej znajdują się w pliku PDF

Iterowanie po obiektach strony

Wszystko zaczyna się od przejścia przez stronę. Ustaw Pdf.PageNumber na stronę, którą chcesz zbadać, a następnie zrób pętlę od 0 do Pdf.PageObjectCount - 1. Każdy obiekt na stronie - ciągi tekstu, ścieżki wektorowe, cieniowanie i obrazy - ma indeks na tej liście. Właściwość Pdf.PageObjectType[I] informuje o rodzaju obiektu, który znajduje się pod tym indeksem

var
  I: Integer;
begin
  Pdf.PageNumber := 1;
  for I := 0 to Pdf.PageObjectCount - 1 do
  begin
    if Pdf.PageObjectType[I] = otImage then
    begin
      // Extract the image at index I
    end;
  end;
end;

Szukasz typu otImage. Gdy go znajdziesz, komponent daje ci dwa sposoby na jego wyodrębnienie. To, który z nich wybierzesz, zależy od tego, co zamierzasz zrobić z obrazem i czy zależy ci na formacie kompresji użytym wewnątrz pliku PDF

Wyodrębnianie jako mapa bitowa Delphi

Najprostszą ścieżką jest metoda Pdf.GetImageBitmap. Przekazuje ona obiekt obrazu do silnika renderującego PDFium, prosi o zdekodowanie dowolnego formatu używanego przez PDF (JPEG, Flate, JPX, JBIG2) i zwraca standardowy obiekt TBitmap z Delphi. Mapa bitowa ma wymiary odpowiadające rzeczywistej wielkości obrazu w pikselach, a nie rozmiarowi, do jakiego został on rozciągnięty na stronie pliku PDF

var
  Bitmap: TBitmap;
begin
  Bitmap := Pdf.GetImageBitmap(I);
  if Assigned(Bitmap) then
  try
    // Draw it, process it, or encode it to another format
    Image1.Picture.Assign(Bitmap);
  finally
    Bitmap.Free;
  end;
end;

Jest to właściwy wybór, gdy zamierzasz narysować obraz na płótnie, pokazać go w kontrolce TImage lub przetwarzać jego piksele w pamięci. Ponieważ otrzymujesz nieskompresowany obraz DIB, możesz go natychmiast przekazać do dowolnej procedury rysowania z biblioteki VCL. Jeśli chcesz zapisać go na dysku, możesz zakodować go do formatu PNG za pomocą klasy TPngImage z Delphi

Jeśli potrzebujesz wymiarów w pikselach przed podjęciem decyzji o wyodrębnieniu obrazu, metody Pdf.ImageWidth i Pdf.ImageHeight zwracają je bez uprzedniego dekodowania danych pikseli

Wyodrębnianie surowych bajtów

Czasami pobranie mapy bitowej jest złą odpowiedzią. Jeśli PDF zawiera obraz JPEG, pobranie go jako mapy bitowej zmusza do ponownego zakodowania go jako pliku JPEG w celu zapisania, co powoduje utratę jakości w tym procesie. Jeśli wyodrębniasz setki obrazów, aby zapisać je w archiwum, potrzebujesz surowych bajtów dokładnie w takiej postaci, w jakiej zostały skompresowane wewnątrz pliku PDF, całkowicie omijając cykl dekodowania/kodowania

Metoda Pdf.GetImageData zwraca dokładnie to, o czym mowa. Przyjmuje indeks obiektu i zwraca tablicę TBytes zawierającą użyteczne dane. Drugi parametr, RawBytes, kontroluje, czy PDFium stosuje filtry obrazu przed zwróceniem danych. Niemal zawsze potrzebujesz tutaj wartości True, aby uzyskać podstawowy format pliku (np. strumień JPEG), a nie nieskompresowane piksele

Aby dowiedzieć się, jakiego rozszerzenia użyć podczas zapisywania tych bajtów, wywołaj Pdf.GetImageFilters. Zwraca on oddzielony przecinkami ciąg znaków oznaczający filtry PDF zastosowane do obrazu. DCTDecode oznacza, że danymi wynikowymi jest format JPEG. FlateDecode zazwyczaj oznacza surowe piksele skompresowane za pomocą biblioteki zlib (często zapisywane w praktyce jako plik PNG, chociaż surowe bajty są tylko strumieniem zlib, a nie sformatowanym plikiem w formacie PNG). JPXDecode to inaczej format JPEG2000

Ścieżka z surowymi bajtami jest szybka i bezstratna dla formatów takich jak JPEG, ale przenosi ona na ciebie ciężar identyfikacji i poprawnego "opakowania" tego formatu. Jeśli filtr zwraca wartość FlateDecode, bajty w tablicy to tylko wartości dla przestrzeni barw RGB lub CMYK skompresowane za pomocą strumienia zlib. Nie możesz po prostu zapisać ich do pliku o nazwie z rozszerzeniem .png i oczekiwać, że przeglądarka go otworzy, ponieważ bajtom brakuje nagłówka specyficznego dla pliku w formacie PNG i odpowiedniej struktury podziału. W przypadku tych formatów innych niż JPEG często pragmatycznym wyborem jest powrót do metody GetImageBitmap i pozwolenie klasie TPngImage z Delphi na prawidłowe zapisanie pliku

Wyodrębnianie wszystkiego ze strony

Typowa procedura wyodrębniająca przechodzi przez obiekty, szuka typu otImage, sprawdza filtry i decyduje, jak pobrać dane. Ten przykład wyodrębnia obrazy JPEG jako surowe bajty, aby uniknąć ponownej kompresji, a wszystkie inne pobiera jako mapę bitową, by mogły zostać zapisane jako plik PNG

uses
  System.IOUtils, Vcl.Graphics, Vcl.Imaging.pngimage;

procedure ExtractImages(Pdf: TPdf; const OutDir: string);
var
  I, ImgCount: Integer;
  Filters, FilePath: string;
  Data: TBytes;
  Bitmap: TBitmap;
  Png: TPngImage;
begin
  ImgCount := 0;
  for I := 0 to Pdf.PageObjectCount - 1 do
  begin
    if Pdf.PageObjectType[I] <> otImage then
      Continue;

    Inc(ImgCount);
    Filters := Pdf.GetImageFilters(I);

    if Pos('DCTDecode', Filters) > 0 then
    begin
      // It's a JPEG. Pull the raw bytes and save as .jpg.
      Data := Pdf.GetImageData(I, True);
      if Length(Data) > 0 then
      begin
        FilePath := TPath.Combine(OutDir, Format('image_%d.jpg', [ImgCount]));
        TFile.WriteAllBytes(FilePath, Data);
      end;
    end
    else
    begin
      // Not a JPEG. Pull as a bitmap and let Delphi encode it to PNG.
      Bitmap := Pdf.GetImageBitmap(I);
      if Assigned(Bitmap) then
      try
        Png := TPngImage.Create;
        try
          Png.Assign(Bitmap);
          FilePath := TPath.Combine(OutDir, Format('image_%d.png', [ImgCount]));
          Png.SaveToFile(FilePath);
        finally
          Png.Free;
        end;
      finally
        Bitmap.Free;
      end;
    end;
  end;
end;

Ponieważ GetImageFilters potrafi zwrócić po kilka filtrów naraz (jak na przykład ASCII85Decode,DCTDecode), najbezpieczniejszą metodą na sprawdzenie wystąpienia określonego parametru będzie funkcja Pos

Ekstrakcja obiektów znajduje tylko te obrazy, które istnieją jako oddzielne obiekty typu XObject w pliku PDF. Nie wyodrębni ona rysunków wektorowych (takich jak logo składające się z linii i krzywych) ani tekstu. Jeśli potrzebujesz obrazu określonego regionu strony bez względu na to, jak został narysowany, jest to zadanie dla renderowania, a nie ekstrakcji. Aby dowiedzieć się, jak wyrenderować stronę do mapy bitowej, zapoznaj się z artykułem towarzyszącym poświęconym konwersji stron PDF na obrazy JPEG

Metody służące do enumeracji oraz ekstrakcji obiektów pokazane w tym dokumencie są częścią Komponentu PDFium dla środowisk Delphi i C++Builder