技術記事

Delphiにおけるソフトマスクと透明度のフラット化

PDF Library for Delphiは、Delphiから両方の種類のPDFソフトマスクを作成できる。SetImageSoftMaskExはマスク画像をターゲット画像へ付加し、事前乗算されたアートワークが必要とする/Matteエントリを書き込むこともできる。NewSoftMaskGraphicsStateはキャプチャされたコンテンツからアルファまたはルミノシティマスクを構築し、それ以降の描画に適用するグラフィックステートを返す。そして消費側が透明度を全く扱えない場合、FlattenTransparencyが視覚的な結果をレンダリングし、各ページをそれで置き換える

ソフトマスクは、グラデーションによるフェード、ビネット、背景に溶け込む写真、そして不透明度が領域内で変化するその他あらゆる効果をPDFが表現する方法である。一定の透明度はグラフィックステート内の単一の数値だが、ソフトマスクは位置に対する関数そのものであり、それを作成できるようになるまでは、デザインのある1つのカテゴリ全体が再現不能である

2種類のマスク、それぞれが読み取るもの

アルファマスクはマスキングコンテンツのアルファチャンネルから値を取る。ルミノシティマスクはマスキングコンテンツの明るさから値を取るため、白い領域は完全に不透明、黒い領域は完全に透明になる。ルミノシティはデザイナーが手を伸ばすものである。グレースケールのグラデーションを描くことがフェードを表現する自然な方法だからである

フォームソフトマスクはキャプチャされたコンテンツから構築され、いくつかの詳細はオプションではなく固定されている。キャプチャされたコンテンツはDeviceRGBの独立した透明グループになるため、マスクは既知の色空間で評価される。したがって背景色はそのグループに合わせて3つの成分を持たなければならない。伝達関数は/Identityまたは有効な関数オブジェクトを受け付け、アートワークを描き直すことなくマスクのカーブを再形成できる

ルミノシティマスクとアルファマスクの両方がNewSoftMaskGraphicsStateへ供給される様子を比較し、グラフィックステートはクリアされるまで持続するという注意を添えた図
ルミノシティマスクは明るさを読み取り、アルファマスクはカバレッジを読み取る。NewSoftMaskGraphicsStateはそのどちらも持続的なグラフィックステートの中に包み込む
uses
  PDFlibrary;

var
  Lib: TPDFlib;
  MaskCapture, MaskGS, PhotoID: Integer;
begin
  Lib := TPDFlib.Create;
  try
    Lib.LoadFromFile('brochure.pdf', '');

    // Page 4にはmask用に描いたgreyscale gradientを保持
    MaskCapture := Lib.CapturePage(4);

    MaskGS := Lib.NewSoftMaskGraphicsState(MaskCapture,
      PDF_SOFT_MASK_LUMINOSITY, '0 0 0', '/Identity');

    Lib.SelectPage(1);
    Lib.SetGraphicsState(MaskGS);
    PhotoID := Lib.AddImageFromFile('hero.jpg', 0);
    Lib.SelectImage(PhotoID);
    Lib.DrawImage(40, 700, 515, 260);        // maskの下でfade out

    // ExtGState settingsは継続するため、scopeを明示的に終了
    Lib.SetGraphicsState(Lib.NewSoftMaskGraphicsState(0,
      PDF_SOFT_MASK_NONE, '', ''));

    Lib.SaveToFile('brochure-masked.pdf');
  finally
    Lib.Free;
  end;
end;

最後のこのステップは飾りではない。グラフィックステートのパラメータは変更されるまで持続し、その後に通常のグラフィックステートを適用してもアクティブなソフトマスクはクリアされない。明示的な/SMask /Noneがなければ、マスクされたアートワークの後に描かれるものはすべてそのマスクを引き継いでしまい、これは視覚的に診断するには特に厄介なバグの1つである

画像マスクとMatteエントリ

画像レベルのマスキングでは、マスク画像はまずDeviceGrayに正規化されるため、どんな色空間で与えられたマスクでも予測可能に振る舞う。任意の/Matteエントリ(画像の色空間に応じて1から4個の成分)は、その画像データがその色に対して事前乗算されていることを消費側に伝え、事前乗算されたアートワークが通常のアルファであるかのように合成された際に現れる暗いハローを防ぐ

Matteエントリが欠けていると暗いハローが生じる一方、宣言されたMatteはきれいに合成される様子を、PDF 1.4およびPDF/A・PDF/Xの安全策と並べて示す図
宣言された/Matteは事前乗算されたアートワークの暗いハローを防ぐ一方、PDF/A-1とPDF/X-1のファイルはいかなるオブジェクトの変更よりも前に拒否される

ここでは2つの安全策が重要である。PDF/A-1とPDF/X-1は透明度を禁じているため、その呼び出しは、後で検証に失敗するファイルを生成するのではなく、いかなるオブジェクトも変更する前に拒否する。そして成功するソフトマスクにはPDF 1.4が必要であり、これはライブラリが自動的に設定する——そのバージョンより前には透明度自体が存在せず、ソフトマスクを含むファイルに1.3のヘッダーを付けることは単純に虚偽になる

繰り返し使われる状態は再利用される:同じキャプチャ済みフォーム、マスクタイプ、背景色、伝達関数の署名は、呼び出しごとに新しいオブジェクトを作るのではなく同じグラフィックステートオブジェクトを生む。100枚の画像に同一のマスクをかけるドキュメントでは、それは決して書き出されない99個のオブジェクトを意味する

フラット化する理由、そして実際に何が行われるか

透明度は今も「画面上では正しく見えるが印刷では誤って見える」という現象の最も一般的な原因である。古いRIP、一部の規制当局向け提出システム、PDF/X-1aはいずれも、生きた透明度が一切ないファイルを要求する

ここでのフラット化は意図的に文字通りのものである:要求された範囲の各ページが選択されたDPIで不透明な背景に対してレンダリングされ、ページのコンテンツとリソースはその単一の画像で置き換えられる。レンダラーが最終的に合成された結果を生成するため、これはすべて——透明グループ、ブレンドモード、ソフトマスク、パターン、入れ子になったフォーム——を一度にカバーし、構造ごとに誤りやすい近似ロジックは一切不要である。ピークのラスタメモリは1ページ分に上限が保たれる

FlattenTransparencyが要求された各ページを300 DPIで不透明な背景に対してレンダリングし、フラット化された印刷用のPDF 1.3ファイルとして保存する様子を示すパイプライン図
要求された各ページは選択したDPIで一度だけレンダリングされ、単一の不透明な画像として、実態に即したPDF 1.3ファイルへ保存される
// page 1〜8を300 DPIで白背景にflattenし、PDF 1.3としてsave
Lib.FlattenTransparency('1-8', 300, $FFFFFF, 0);
Lib.SaveToFile('press-ready.pdf');

トレードオフも同様に文字通りである:出力は画像であるため、テキストはもはや選択も検索もできず、ベクターのエッジは選択したDPIで再サンプリングされる。フラット化は編集ステップではなく配信ステップである。透明な原本は保持し、コピーをフラット化すること

回転したページ、ユーザー単位、ページボックス

回転したページには、素朴な実装が見落とす注意が必要である。レンダリング後、視覚的な幅と高さがMediaBoxへ書き戻され、ユーザー単位は実効DPIと新しいページサイズへ畳み込まれ、/Rotateエントリ、ユーザー単位、補助的なページボックスは削除される

それを省略すると、フラット化された画像自体は正しいのに、ビューアがそれを再度回転させてしまう、あるいはもはや適用されないユーザー単位でスケーリングしてしまうファイルができてしまう。ボックスを再計算するのではなく削除することが正しい選択である。フラット化前のジオメトリを記述するTrimBoxは、TrimBoxが全くない状態よりも悪いからである

注釈とフォームフィールドはデフォルトで静的画像へレンダリングされ、対話的なオブジェクトは削除される。これは印刷用の成果物が望むものである。代わりに対話的なオブジェクトを保持する場合、レンダラーの注釈パスはオフになるため、何かが二重に描かれることはない

すべてを走査するバージョンチェック

PDF 1.3として保存するには、どこにも透明度が残っていないことが必要であり、保存前のチェックはページから到達可能なオブジェクトだけでなく、すべての間接オブジェクトを検査する。これは最初に見えるより厳格だが正しい:以前の編集で取り残された孤立したソフトマスクは今もファイル内にあり、ページではなくオブジェクトを走査する消費側はそれを見つけてしまう

このダウングレードを実現可能にするため、対象のすべてのページが正常に置き換えられた後、デフォルトでマークアンドスイープが実行され、到達不能なオブジェクトが解放される。計画しておくべき帰結は、そのスイープの前に取得した画像・キャプチャ済みページ・グラフィックステートの識別子が、その後は無効になっている可能性があることである。編集を続ける前に必要なリソース識別子を再取得するか、スイープを無効化してバージョンをその後下げられないことを受け入れること

これを補う2つの関連機能がある。マスクではなくシェーディングとグラデーションを使うベクターアートワークについてはベクターグラフィックス、シェーダー、グラデーションで扱われており、その効果がシェーディングとして表現できる場合はしばしばそちらの方が良い経路である。そしてフラット化する理由が透明度ではなく色の要件である場合、ドキュメントを別の色空間へ再カラー化するにある変換経路であれば、フラット化では失われてしまう生きたテキストを保持できる

ソフトマスクの作成、フラット化、そしてその背後にあるレンダリングエンジンは、Delphi、C++Builder、Free Pascal向けの1つのライブラリにまとめられている。機能一覧の全体はPDF Library for Delphiページにある