Technical Article

Delphi PDFでのネイティブJBIG2 2値画像圧縮

スキャンされた契約書は、白い紙に黒いインクが1インチあたり数百ドットの解像度で印刷されたものです。1ピクセルあたり1ビットのビットマップとして保存すればすでに十分に小さいですが、そのようなページが100枚もあると、PDFはメールで送信できるサイズを簡単に超えてしまいます。適切なフィルタを使用すれば、この計算は変わります。JBIG2は、ISO 32000-1が2値画像用に定義している最も圧縮率の高い形式であり、スキャンされたテキストの束に対して、CCITT Group 4の出力サイズを日常的に半減させます。これは、入力がFAXやスキャン、あるいは2色に縮小された画像である場合に選ぶべきフィルタであり、HotPDFはこれをPDFに直接書き込むことができます

このフォーマットは、汎用的な画像コーデックにはない2つのアイデアによってこの圧縮率を実現しています。1つは、白い背景に対して黒い部分がどのように配置されるかをモデル化すること、もう1つは、スキャンされたページの大半が同じ数百個のグリフ形状を何千回も繰り返していることに着目することです。この両方を理解することで、推測ではなく意図的にエンコードのオプションを選択できるようになります

PDF仕様におけるJBIG2の位置づけ

ISO 32000-1の§7.4.7にあるストリームフィルタのリストにはJBIG2Decodeが含まれており、PDF 1.4以降で利用可能です。これが適用されるのは、/BitsPerComponentが1で、カラースペースが単一のチャンネルに解決される画像XObjectの1か所のみです。ここが最も重要な点です。JBIG2は2値コーデックであるため、写真においてDCTやJPXDecodeと競合することはありません。競合するのは、ドキュメントスキャナが生成するような2階調のページに対するCCITTFaxDecode(Group 3およびGroup 4のFAXフィルタ)です

デコーダは、標準でPDFプロファイルと呼ばれる埋め込みのJBIG2構造を消費します。このプロファイルでは、各画像ストリームは単なるビットストリームではなくセグメントのシーケンスを保持します。オプションの/JBIG2Globalsストリームには、同一ドキュメント内の複数の画像で共有されるセグメントが含まれており、このメカニズムにより、繰り返されるコンテンツをページごとではなくファイル全体で1回だけ保存できます。HotPDFはデフォルトでページごとのストリームを出力し、バックエンドからの要求がない限りglobalsチャンネルを空のままにします

バックエンド優先のエンコーダアーキテクチャ

完全なJBIG2エンコーダは大規模なソフトウェアであり、その最も積極的な部分は歴史的に特許に阻まれ、すべての製品に適さないライセンスで提供されてきました。HotPDFは、インターフェイスとエンジンを分離することでこの問題を解決しています。HPDFJBIG2ユニットはライブラリの他の部分が行う呼び出しを定義し、デフォルトの控えめな組み込みエンコーダを提供するため、JBIG2はすぐに動作します。実稼働レベルの圧縮率が必要な場合は、より強力なエンジンを登録することで、呼び出し側のコードを変更することなくライブラリが処理を委譲します

この切り替えは1回の登録呼び出しで行われます。バックエンドが登録されていない場合、エンコーダは組み込みの処理ルートにフォールバックします。バックエンドを1つ登録すると、それ以降のエンコードはすべてそこを通って実行されます

uses
  HPDFJBIG2;

// Query what is active, then optionally install a stronger engine.
if not IsJBIG2EncoderBackendAvailable then
  // Production backend not present: HotPDF uses its built-in MMR path.
  RegisterJBIG2EncoderBackend(MyVendorJBIG2Encode);

// Later, to return to the built-in behaviour:
// ClearJBIG2Backends;

デコード用にもRegisterJBIG2DecoderBackendを介した同じフックが存在し、IsJBIG2DecoderBackendAvailableを使用して調査することができます。これが、単一の巨大なエンコーダではなく、ライブラリが小さな組み込みの処理ルートとバックエンドのシーム(継ぎ目)を提供する理由です。組み込みの処理ルートはバイナリを軽量に保ちライセンスの問題を回避しますが、シームは完全なエンコーダのライセンスを取得したチームが、PDF書き込みレイヤーに一切触れることなくそれを組み込むことを可能にします

エンコードオプションが実際にトレードオフするもの

エンコードは、LosslessUseGlobalSegmentsUseSymbolDictionaryLossyLevelの各フィールドを持つレコードであるTJBIG2EncodeOptionsを通じて設定されます。コンポーネント向けのラッパーであるTHPDFJBIG2Optionsは、オブジェクトインスペクタから設定できるようにLosslessUseSymbolDictionaryLossyLevelを公開し、内部でそれをレコードに変換します。これらの設定は3つの目的によって決定されます

可逆的(ロスレス)な再構築では、すべてのピクセルが保持されます。LosslessをTrueに設定し、LossyLevelをゼロのままにしておくと、デコードされたビットマップは入力と完全に一致します。これは、線画や技術図面、また署名や印鑑など、ピクセルの欠落が意味を変えてしまうようなページにおいて唯一の安全な選択肢です。シンボル辞書コーディングは、テキストを意識した重複排除を有効にするものであり、JBIG2とFAXフィルタを区別するオプションです。0から9までの整数であるロッシーレベルを指定することで、能力のあるバックエンドは、ほぼ同一のマークを同じシンボルとして扱うことによって、忠実度とサイズのトレードオフを行うことができます。ゼロは可逆を意味します。組み込みのエンコーダは可逆の処理ルートのみを尊重し、ゼロ以外のロッシーレベルを無視するため、より高いレベルは、それを実装するバックエンドが登録された場合にのみ有効になります

var
  Options: TJBIG2EncodeOptions;
begin
  Options := DefaultJBIG2EncodeOptions;   // Lossless True, symbol dictionary on
  Options.Lossless := True;
  Options.LossyLevel := 0;                // 0 keeps every pixel
  Options.UseSymbolDictionary := True;    // dedupe repeated glyphs
  // Pass Options to a backend, or let THPDFJBIG2Options carry them.
end;

シンボル辞書とテキストスキャンが有利な理由

スキャンされたテキストのページは、実際には単語の画像ではありません。それは、同じ文字のe、同じt、同じカンマが数百回印刷されたものであり、それぞれのインスタンスは1つの基本となる形状のわずかにノイズを含んだコピーです。シンボル辞書はそのような構造を捉えます。エンコーダは、ページ上の異なるマークを辞書に集め、各形状を1回だけ保存し、辞書のエントリを参照する位置のリストとしてページを記録します。同じグリフが1,000回出現した場合のコストは、保存された1つのビットマップと、安価な1,000回の配置だけで済みます

これこそが、JBIG2がCCITT Group 4を凌駕する部分です。Group 4はグリフという概念を持たず、各スキャンラインをその上のラインと対比してコーディングするため、文字が現れるたびにその文字の完全なコストを支払うことになります。JBIG2は一度だけ支払います。同じ辞書がドキュメントレベルのglobalsストリームに昇格されると、複数のページにまたがるスキャン全体で節約が複利的に働きます。なぜなら、ページをまたいで共有される形状がファイル全体で1回だけ保存されるからです。密集したテキストではその差はわずかではありません。それこそがJBIG2が存在する理由です

その他のすべてに対応する汎用領域とMMR

すべての2値画像がテキストであるとは限りません。地図、回路図、技術図面、および混合ページには、いかなる辞書でも要約できない線画が含まれています。これらについて、JBIG2はシンボルのトレーニングなしで直接圧縮されたピクセルの長方形である汎用領域をコーディングします。標準では、汎用領域でMMRを使用することが許可されています。これは、各ピクセルの行をその上の行と対比してモデル化する、Group 4 FAXで既に使用されている修正MR(Modified Modified READ)コーディングです

これがHotPDFが組み込みエンコーダで提供している処理ルートです。バックエンドが登録されておらず、要求が可逆(ロスレス)である場合、ライブラリはビットマップを単一のMMR汎用領域として圧縮し、PDFプロファイルが要求するJBIG2セグメント構造でラップします。これには辞書も、トレーニングパスも、参照するための2番目の画像も必要ないため、線画や混合2値コンテンツに対する信頼できるデフォルトとなります。純粋なテキストにおいて完全なシンボル辞書エンコーダに匹敵することはありませんが、常に正しく、常にロスレスで、常に存在します。これに対するエンコーダのインターフェイスは1回の呼び出しです

var
  Encoder: THPDFJBIG2Encoder;
  ImageData: TJBIG2ByteArray;
  Scanlines: TJBIG2ScanlineArray;  // one byte array per row, MSB-first
  W, H: Integer;
begin
  // Scanlines, W and H describe a 1-bit page; each row is (W + 7) div 8 bytes.
  Encoder := THPDFJBIG2Encoder.Create;
  try
    if Encoder.EncodeToByteArray(Scanlines, W, H, ImageData) then
      // ImageData now holds a JBIG2 stream ready for a /JBIG2Decode XObject.
      ;
  finally
    Encoder.Free;
  end;
end;

ドキュメント構築時に有効にする

日常的な使用において、エンコーダクラスに直接触れることはありません。HotPDFは、ドキュメントの画像圧縮の選択肢としてJBIG2を公開しています。THPDFImageCompressionType列挙型には、Flate、JPEG、およびCCITTの各オプションと並んでicJBIG2が含まれており、ドキュメントには、その圧縮が選択されたときに使用される設定を保持するTHPDFJBIG2Options型のJBIG2Optionsプロパティがあります。この方法で圧縮したい2値画像を追加する前に、両方を設定してください

var
  Pdf: THotPDF;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.ImageCompressionType := icJBIG2;     // route 1-bit images through JBIG2
    Pdf.JBIG2Options.Lossless := True;        // keep every pixel
    Pdf.JBIG2Options.UseSymbolDictionary := True;
    Pdf.JBIG2Options.LossyLevel := 0;
    // Add pages and place your scanned 1-bit images here.
  finally
    Pdf.Free;
  end;
end;

注目に値する便利な機能の1つは、TDBGridを直接PDFにレンダリングするDBGridHotPDFExportアドオンです。その出力の大半は2値の罫線とテキストであるため、JBIG2用に設定されたドキュメントでは、特別な処理を行うことなく出力がコンパクトに保たれます。このブログの関連する2つのトピックでは、その周辺のワークフローについてさらに深く掘り下げています。レポートの構築時に画像やフォントがどのように配置されるかについては、Delphiでのフォントと画像を含むレポート出力を参照してください。圧縮されたドキュメントがアーカイブプロファイルを満たす必要がある場合、DelphiでのPDF/A、PDF/X、およびPDF/UAの検証にあるルールが、特定の適合レベルでどのフィルタが許容されるかを示しています。JBIG2は、DelphiおよびC++Builder向けのHotPDF Componentの一部として、このブログの他の場所で取り上げている読み込み、編集、暗号化のAPIとともに提供されています