Teknik Makale

Delphi'de PDFium Bileşeni ile PDF'lerden Görüntü Çıkarma

PDF, görüntüleri içerik akışlarının içinde birinci sınıf nesneler olarak depolar. Bir sayfa bir fotoğrafa, bir taramaya veya bir diyagrama başvurduğunda, piksel verileri sayfa geometrisinin yanında bir XObject sözlüğünde yaşar. PDFium Bileşeni bunu TPdf üzerindeki iki özellik aracılığıyla ortaya çıkarır: mevcut sayfada kaç tane gömülü bit eşlem olduğunu döndüren BitmapCount ve bunlardan birini sahip olduğunuz ve serbest bırakmanız gereken bir TBitmap içine çözen Bitmap[Index]. Çıkarma modelinin tamamı budur. Döngü dört satırdır; asıl yargı gerektiren kısım etrafındaki tesisattır

Belgeyi açma

TPdf hakkında bilinmesi gereken ilk şey, Active := True ifadesinin hiçbir zaman hata (exception) fırlatmadığıdır. Yükleme hataları, yanlış şifreler, bozuk dosyalar: hepsi dahili olarak yutulur ve bileşen basitçe etkin kalmaz (inactive). Atamadan sonra bayrağı kendiniz kontrol etmelisiniz, aksi takdirde PageCount sıfır döndürerek sayfa döngüsüne girersiniz ve neden hiçbir şeyin çıkarılmadığını merak edersiniz

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;

Parola korumalı dosyalar aynı modeli izler: Active := True ayarlamadan önce Pdf.Password atamasını yapın. Parola yanlışsa Active, False kalır ve yakalayacak hiçbir istisna (exception) almazsınız. Yüzlerce dosyayı işleyen bir toplu iş aracında, bu sessiz davranış aslında yararlıdır: her biri için çağrı yığınını geri sarmak (unwind) yerine, hataları bir listede biriktirirsiniz

Sayfaları yineleme ve bit eşlemleri çekme

BitmapCount sayfa başınadır, bu yüzden okumadan önce Pdf.PageNumber değerini ayarlarsınız. Sayfa numaraları 1 tabanlıdır; varsayılan değer 0'dır, yani hiçbir sayfa yüklenmemiştir. Bitmap[Index] özelliği 0 tabanlıdır ve çağırana ait (caller-owned) bir TBitmap döndürür. Onu serbest bırakmalısınız (free). Büyük bir belge üzerindeki uzun bir döngüde free işlemini ihmal ederseniz bellek hızla tırmanır, çünkü her bir bit eşlem herhangi bir sıkıştırmadan önce birkaç megabaytlık ham piksel verisi olabilir

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 koruması önemlidir. Az sayıda PDF üreteci, görüntü XObject'lerini sıfır piksel boyutlarıyla veya başka bir şekilde hatalı biçimlendirilmiş verilerle yazar; bu durumlarda bileşen boş bir bit eşlem yerine nil döndürür. Bir nil dönüşünü hata olarak değerlendirmek ve çıkarmayı durdurmak yanlış bir reflekstir: atlayın, denetim izine (audit trail) ihtiyacınız varsa sayfayı ve dizini günlüğe kaydedin ve devam edin. Sayfanın geri kalanı yine de geçerli resimler verebilir

Dış döngünün her yinelemede Pdf.PageNumber değerini ayarladığına dikkat edin. Bu atama, sayfayı bileşenin iç durumuna yükleyen ve BitmapCount değerini anlamlı kılan şeydir. Bunu atladığınızda aynı sayfanın sayısını tekrar tekrar okursunuz. Modeli yazarken gereksiz gibi görünür, ancak API bu şekilde tasarlanmıştır: sayfa bir koleksiyon değil, bir imleçtir

Bir çıktı formatı seçme

BMP kayıpsızdır ve ek birimler (units) olmadan her zaman kullanılabilir, bu da görüntünün ne içerdiğini henüz bilmediğinizde sağlam bir varsayılan olmasını sağlar. Dosya boyutu önemli olduğunda, döndürülen TBitmap öğesinin piksel formatı size hangi kodeğin (codec) uygun olduğunu söyler. 32 bitlik bir bit eşlem bir alfa kanalı taşır; PNG bunu kayıpsız korur. Kesintisiz tona sahip büyük bir 24 bit görüntü JPEG için adaydır. Daha küçük resimlerin veya sınırlı bir paletle çizilmiş resimlerin, düşük kalite ayarlarında engelleme (blocking) kalıntıları ekleyen ve yüksek kalite ayarlarında çok az tasarruf sağlayan JPEG'den geçirilmesindense genel olarak BMP olarak bırakılması daha iyidir

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;

Uygulamada, format seçimi Bmp.PixelFormat ve boyutlar tarafından yönlendirilir. PixelFormat = pf32bit ise alfa taşıyan bir formata ihtiyacınız vardır; eski Delphi sürümlerinde PNGImage birimini (unit) gerektirmesine rağmen bariz seçim PNG'dir. Kabaca 300 pikselden daha geniş 24 bitlik resimler için, 85 kalitedeki JPEG, çoğu fotoğrafik içerikte algılanabilir bir kayıp olmadan BMP'ye kıyasla bire üç oranında boyut küçültme sağlar. Bu eşiğin altında BMP boyut olarak karşılaştırılabilirdir ve herhangi bir kalite kararından tamamen kaçınır

BitmapCount neyi sayar neyi saymaz

PDF, resim XObject'leri ile yol (path) operatörleriyle çizilen vektör grafikleri arasında ayrım yapar. Görsel olarak karmaşık görünen bir sayfa, her eleman vektör ise BitmapCount olarak sıfır döndürebilir. Taranan sayfalar neredeyse her zaman tam olarak bir tane döndürür: tarayıcı, tüm taramayı tarayıcının ayarlandığı çözünürlükte tek bir tam sayfa görüntü XObject'i olarak yazar. Dizgi metni (typeset text) gömülü fotoğraflarla karıştıran sayfalar, fotoğraf başına bir girdi döndürür. Dekoratif kural çizgileri, gölgeli arka planlar ve tablo kenarlıkları genellikle bit eşlem sayısında (bitmap count) hiç görünmez

Sayı aynı zamanda satır içi görüntüleri (inline images), görüntü verilerinin adlandırılmış bir XObject yerine doğrudan sayfa içeriği akışına gömüldüğü nadiren kullanılan bir PDF yapısını da içermez. Bunlar bu API'nin ortaya çıkardıklarının dışındadır; gerçek belgelerde çoğu çıkarma aracının onları işlemediği kadar nadirdirler

Akılda tutulmaya değer bir detay: okuduğunuz BitmapCount son PageNumber atamasından itibaren mevcut sayfa içindir. Kodunuz dallanırsa veya sayma ile getirme (fetching) arasında PageNumber'ı değiştiren herhangi bir işlevi çağırırsa, yer ayırdığınızdan daha az resim okuyabilir veya sonun ötesini dizinleyebilirsiniz (index past the end). Arada PageNumber'a dokunmadan aynı sayfadaki sayı okumasını (count read) ve Bitmap[] döngüsünü koruyun

Bir form uygulamasında TPdfView kullanma

TPdfView bileşeni aynı BitmapCount ve Bitmap[] özelliklerini sunar, ancak okuduğu sayfa TPdf.PageNumber değil, görünümün şu anda görüntülenen sayfasıdır. İki sayfa işaretçisi (pointer) bağımsızdır; birini ayarlamak diğerini hareket ettirmez. Canlı bir görüntüleyiciye sahip bir VCL form uygulamasında, görüntüleyici kullanıcının son kaydırdığı yerde kalırken TPdf üzerinden çıkarmayı yürütmek için Pdf.PageNumber := N çağrısı yapabilirsiniz. Bu ayrım kasıtlıdır ve bir arka plan çıkarması çalışırken görüntüleyicinin görüntüleme durumunu temiz tutar

Toplu işlerde bellek ve performans

Büyük bir arşivde izlenmesi gereken asıl şey bellek bütçesidir. Her Bitmap[] çağrısı yığın üzerinde (heap) yeni bir TBitmap ayırır ve taranmış 300 DPI'lik bir sayfada bu, herhangi bir kodlamadan önce kolayca 25 MB'lık ham piksel verisidir. Sayfaları yinelemeler arasında serbest bırakmadan (free) sıkı bir döngüde işlerseniz, çalışma seti (working set) görüntü sayısıyla doğrusal olarak büyür. Doğru şekil her zaman şudur: bir bit eşlemi getirin, ne gerekiyorsa yapın, serbest bırakın (free), sonrakini getirin. Karşılaştırma adımı için aynı anda birkaç bit eşlem başvurusunu (reference) tutmanız gerekiyorsa, önce bunları BitmapCount ile sayın ve kapsayıcınızı (container) buna göre tahsis edin, ardından belge sonu temizliğine ertelemek yerine işiniz biter bitmez her birini serbest bırakın (free). Taranmış 500 sayfası olan bir belgede bu ayrım, en yüksek RSS (peak RSS) için 25 MB ile 12 GB arasındaki fark anlamına gelebilir

Burada gösterilen BitmapCount ve Bitmap[] özellikleri, Delphi ve C++Builder için PDFium Bileşeni'nin bir parçasıdır