Technical Article

Type 0サンプル関数を使用したPDFでの3DカラーLUT

PDF関数は、仕様書の中で最も目立たない領域の1つです。多くの開発者は、Type 2アキシャルシェーディングで2つのカラー間をフェードさせるために必要なものとして一度見かけるだけで、二度と目を向けないかもしれません。しかし、この関数機能は、シェーディング、転送関数、ハーフトーンスポット関数、特色のグラデーション(Separation tints)、およびソフトマスク転送カーブなどに再利用される、小規模な汎用評価器です。4つの関数タイプの中で、Type 0は最も強力であり、最も理解されていないものです。これはサンプル関数(sampled function)であり、読者がその間を補間する出力値の多次元グリッドです。グリッドには任意の数値を格納できるため、Type 0関数は任意の非線形マッピングを表現でき、これはカラー検索テーブル(Color LUT)の正確な形状となります。

この記事では、ISO 32000-1の§7.10.2で定義されているType 0辞書について説明し、ドキュメントパイプラインで最も重要となる2つのケースについて説明します。3つの入力を持つRGB-to-RGBカラー補正LUTと、1つの入力を持つ特色の網点変換です。どちらも同じサンプル関数ビルダーを使用し、その違いはグリッドが持つ入力の数だけです。

サンプル関数:リーダーが補間するグリッド

Type 0関数は、規則的なグリッド上にサンプルを格納し、それらの間を補間することによって、m入力のベクトルをn出力のベクトルにマッピングします。ISO 32000-1 §7.10.2には、そのグリッドを記述するキーがリストされています。/Domainは入力ごとに2つの数値(各入力軸の下限と上限)を保持します。/Rangeは出力コンポーネントごとに2つの数値を保持します。/Sizeは各入力軸に沿ったサンプル数を示すm個の整数の配列です。したがって、3次元で各辺が12サンプルのグリッドは/Size [12 12 12]となり、1,728個 of グリッドポイントを格納します。/BitsPerSampleは格納される各値の精度を設定します。HotPDFは、表38が許可する値と一致する1、2、4、8、12、16、24、および32ビットを受け入れます。

サンプルストリームは決まった順序で読み取られます。最初の入力次元が最も速く変化し、次に2番目の入力次元と続きます。各グリッドポイントにおいて、n個の出力コンポーネントが順番に格納されます。8ビットで3入力のRGB-to-RGBテーブルの場合、赤出力、緑出力、青出力の順に並んだグリッドポイントあたり3バイトで、赤入力を最初にスキャンします。さらに2つのキーが、連続する世界を整数のグリッド上にマッピングします。/Encodeは、各入力をその/Domain区間からサンプルインデックス範囲である0Size[i] - 1にマッピングし、/Decodeは格納された生の整数を/Range区間にマッピングします。これらをデフォルトのままにしておくと、[0 1]の範囲の入力が完全なグリッド上にきれいに配置され、格納された255のバイトが最大出力範囲にデコードされます。これは、[0,1]に正規化されたカラーLUTが要求する動作です。

Order 1とOrder 3の違い

グリッドポイント間において、リーダーは補間を実行する必要があり、/Orderでその方法を選択します。/Order 1は多重線形補間(multilinear interpolation)です。1軸に沿って線形、2軸でバイリニア、3軸でトリリニア補間を行います。処理が高速で、多くのビューアのハードウェアが行う処理そのものであり、滑らかなカラートランスフォームにおいては、より高度な処理と見分けがつかないのが一般的です。/Order 3は3次スプライン補間(cubic-spline interpolation)を要求します。これは、より多くの処理コストと、評価ポイントの周囲のより広いサポート領域を犠牲にして、サンプル間により滑らかな曲線をフィッティングします。

トレードオフは、グリッドの密度と曲線の滑らかさです。グリッドが粗く、マッピングに目に見える曲率がある場合、3次補間はその効果を発揮します。遠く離れた2つのサンプル間の直線的な補間は、グラデーション上で目につくトーンカーブの平坦化を引き起こす可能性があるためです。グリッドが高密度になれば、セグメントが十分に短くなるため、線形補間でも曲線を忠実に追従でき、3次のメリットは少なくなります。実用的なルールとしては、グリッドが小さい場合やトランスフォームが急峻な場合にのみ/Order 3を採用し、それ以外はデフォルトの線形補間のままにしておきます。なお、/OrderはType 0関数にのみ適用され、HotPDFは1または3以外の値を受け付けません。

3D LUT:3つの入力、3つの出力

An RGB-to-RGB color correction... 「RGB-to-RGBのカラー補正は、カラーグレーディングやデバイスマッチングで使用されるクラシックな3D LUTであり、3つの入力を持つグリッドの典型的なケースです。立方体の各軸が1つの入力チャネルであり、すべてのグリッドポイントはその入力座標に対して補正されたRGBの3つ組(トリプル)を格納し、リーダーは入力されたカラーを囲む周囲のサンプルの間でトリリニア補間を実行します。ここでは3つの入力が不可欠です。補正後の赤は、入力の赤だけでなく、入力の緑と青にも依存する可能性があるためです。チャネルごとのカーブではチャネル間のクロストークを表現できませんが、3Dキューブであれば可能です。

HotPDFは、/Domain/Range/Size/BitsPerSample、およびサンプルのバイト配列を直接受け取るRegisterSampledFunctionを介してType 0ストリームを構築し、関数オブジェクトを返します。標準的な正規化キューブの場合、3つの入力軸すべてと3つの出力すべてに[0,1]の境界を指定し、解像度N x N x Nとフラット化されたサンプルテーブルを渡します。ビルダーはバイト数がグリッドと一致しているかを検証します。バイトアライメントされた深度に対して、OutputCount x (BitsPerSample div 8) x サイズの積を期待し、配列の長さが正しくない場合は例外を発生させます。そのため、ストライドの計算ミスは後で破損した状態で描画されるのではなく、登録時に即座にエラーになります。

const
  N = 17;  // 17 x 17 x 17 cube, the common ICC LUT resolution
var
  LutFn: THPDFStreamObject;
  Samples: TBytes;
begin
  // Fill Samples with N*N*N grid points, 3 bytes each (R,G,B output),
  // red input varying fastest. Build the corrected triple for each
  // grid coordinate with your ICC-managed conversion, then store it.
  SetLength(Samples, N * N * N * 3);
  BuildCorrectedCube(Samples, N);   // your color-managed fill

  LutFn := Pdf.RegisterSampledFunction(
    [0,1, 0,1, 0,1],   // /Domain: three input axes on [0,1]
    [0,1, 0,1, 0,1],   // /Range:  three output channels on [0,1]
    [N, N, N],         // /Size:   the cube resolution per axis
    8,                 // /BitsPerSample
    Samples,
    1);                // /Order 1 = trilinear
end;

キューブの色度的な正確さは、グリッドへの値の設定方法に依存し、PDF関数自体には依存しません。正しい方法は、ソフトプルーフを駆動するのと同じエンジンであるICC管理下の変換を介して各グリッドポイントを計算することです。これにより、グリッド内の数値が定義されたソースプロファイルおよびデスティネーションプロファイルに対して意味を持ちます。変換の境界となるプロファイルをRegisterICCProfileで登録します。これはICCBasedカラースペース(1、3、または4コンポーネント)を記録し、LUTが供給するコンテンツにアタッチできるリソース名を返します。Type 0関数は補間テーブルを保持し、ICCプロファイルは端点の意味を保持します。

1Dのケース:特色の網点変換

Separation(特色)カラースペースは、まったく異なるタスクのために同じ仕組みを利用します。ISO 32000-1 §8.6.6.4で定義されているSeparationスペースは、インク名と網点変換(tint transform)をペアにすることで、PANTONEやニスなどの単一の着色剤を表します。網点変換とは、インクなしの0からインク100%の1までの1次元の網点値を、デバイスが実際に描画できる代替のカラースペース(通常はCMYK)にマッピングする関数です。その網点変換には頻繁にType 0関数が使用され、この場合のグリッドは正確に1つの入力軸を持ちます。

これが3D LUTとの明確な対比です。特色インクは1つの自由度であるため、その網点変換に必要な入力は1つであり、グリッドはサンプルのラインになり、各サンプルはその網点レベルでのCMYK(またはその他の代替)値を保持します。RGBキューブは、ドメインが3次元でありチャネルが相互に作用するため、3つの入力が必要です。同じ関数タイプ、同じ補間ルールでありながら、次元数が異なります。仕様は1つの評価器を再利用し、/Sizeによってライン、平面、または立体を探索しているかを決定します。HotPDFはSeparation全体をRegisterSeparationLUTでラップします。これは、内部的にフラットなバイト配列から1入力のType 0網点変換を構築し、カラースペースのリソース名を返します。

var
  SpotCS: AnsiString;
begin
  // Four CMYK output bytes per tint grid point, tint domain [0..1].
  // Here 0% ink -> all zero, 100% ink -> a rich spot build,
  // with two interior steps; the tint transform interpolates between.
  SpotCS := Pdf.RegisterSeparationLUT(
    'PANTONE 286 C',         // colorant name
    'DeviceCMYK',            // alternate color space
    [  0,   0,   0,   0,     // tint 0.00 -> 0,0,0,0
      90,  60,   0,   0,     // tint 0.33
     100,  80,   0,  10,     // tint 0.66
     100,  72,   0,  18]);   // tint 1.00 -> full ink build
  // Use SpotCS with SetFillColorSpace / SetFillColor on a page.
end;

サンプル数は、代替カラースペースのコンポーネント数の正の倍数である、整数のグリッドポイント数でなければなりません。また、補間するセグメントが存在するために、少なくとも2つのポイントが必要です。CMYK代替スペースに対してポイントあたり3バイトを渡すと、3Dビルダーが適用するのと同じ防御的な検証によって拒否されます。これは、印刷時に静かに失敗するような事態を防ぐために必要な動作です。

同じ仕組みが再登場する場所

Type 0を汎用的な補間テーブルとして捉えると、デバイス制御に関するさらに2つの機能が特別なケースには見えなくなります。転送関数(transfer function)は出力デバイスに向かう途中でコンポーネントの値を調整するもので、チャネルごとの単なる関数です。HotPDFはこれをRegisterTransferFunctionStateを介してExtGStateとして登録します。これは、1つの結合された関数、またはチャネルごとの関数の配列を受け入れます。これらの関数は通常の関数オブジェクトであるため、RegisterSampledFunctionが返すTHPDFStreamObjectそのものを渡して、数式ではなくサンプルテーブルから転送曲線を制御できます。

var
  ToneFn: THPDFStreamObject;
  GsName: AnsiString;
begin
  // A single-input, single-output sampled tone curve on [0,1].
  ToneFn := Pdf.RegisterSampledFunction(
    [0,1], [0,1], [256], 8, ToneCurveBytes, 1);

  // Apply it to all channels as a combined /TR2 transfer function.
  GsName := Pdf.RegisterTransferFunctionState(ToneFn, []);
  // Select GsName on the page before drawing the affected content.
end;

黒版生成(Black generation)と下色除去(undercolor removal)も同じファミリーに属します。デバイスがRGBをCMYKに変換する際、グレーコンポーネントのうちどの程度を黒インクとして保持するかを決定します。仕様はこの決定を関数(グラフィックス状態辞書の/BG2および/UCR2エントリ。それぞれ計算されたグレー値から黒の量への1入力の曲線)として表現します。解析的な式ではなく測定された曲線を使用したい場合、これらもType 0関数となり、同じ方法でRegisterSampledFunctionを通じて作成され、グラフィックス状態に配置されます。覚えておくべき教訓は、PDF関数はカラー管理を実行する場所ではなく、実際のカラーエンジンで行った決定を保持する検索テーブルにすぎないということです。そしてType 0は、任意の決定を保持するのに十分な柔軟性を持つ唯一の関数タイプです。

フォント、画像、カラーリソースが完成したドキュメントに出力される仕組みの詳細については、フォントと画像を使用したレポート出力のチュートリアルを参照してください。出力がアーカイブまたは印刷プリフライトチェックに合格する必要がある場合、PDF/A、PDF/X、およびPDF/UA検証ガイドでカバーされているカラースペースと出力インテントのルールが、これらの関数のうちどれが許可され、デバイスカラーがどのようにタグ付けされるかを規定します。これらすべてが、同じType 0コア上に構築されたシェーディング、ICC、およびSeparation APIと並んで、DelphiおよびC++Builder向けのHotPDF Componentに含まれています。