PDF Function 是規格中較安靜的角落之一。大多數開發者只碰過它一次,當作 Type 2 軸向著色在兩個顏色之間漸變時需要的東西,之後就再也沒回頭看過。這很可惜,因為函式機制是一個小型的通用評估器,格式把它重複用在著色、轉換函式、網屏專色函式、分色色調,以及軟遮罩轉換曲線上。在四種函式類型中,Type 0 最強大,也最不被理解。它是一種取樣函式:讓讀者在多維輸出值網格之間做插值。因為網格可以放入任何數字,Type 0 函式可以表示任意非線性對應,這正是色彩查找表的精確形狀
本文先走過 ISO 32000-1 在 §7.10.2 定義的 Type 0 字典,接著看文件管線中最重要的兩個案例:三輸入的 RGB 對 RGB 色彩校正 LUT,以及單輸入的專色色調轉換。兩者都能用同一個取樣函式建立器完成,差別完全在於網格有多少個輸入
取樣函式是一個讓讀者插值的網格
Type 0 函式會把 m 輸入向量對應到 n 輸出向量,方法是在規則網格上儲存樣本,然後在樣本之間插值。ISO 32000-1 §7.10.2 列出描述這個網格的鍵。/Domain 為每個輸入保留兩個數字,也就是每個輸入軸的下限和上限。/Range 為每個輸出元件保留兩個數字。/Size 是一個含有 m 個整數的陣列,用來給出每個輸入軸上的樣本數,所以三維中每邊 12 個樣本的網格會有 /Size [12 12 12],並儲存 1,728 個網格點。/BitsPerSample 設定每個儲存值的精確度;HotPDF 接受 1、2、4、8、12、16、24 和 32 位元,和表 38 允許的值一致
樣本資料流會依固定順序讀取。第一個輸入維度變化最快,接著是第二個,以此類推,而每個網格點上的 n 個輸出元件則按順序儲存。對 RGB 對 RGB 的查找表來說,這代表在 8 位元下每個網格點有 3 個位元組,布局是紅輸出、綠輸出、藍輸出,並先掃描紅色輸入。還有兩個鍵把連續世界對應到整數網格。/Encode 把每個輸入從 /Domain 區間對應到樣本索引範圍 0 到 Size[i] - 1,而 /Decode 把原始儲存整數對應回 /Range 區間。把它們留在預設值時,跨越 [0 1] 的輸入會乾淨地落到整個網格上,而 255 的儲存位元組會解碼到輸出範圍頂端,這正是標準化 [0,1] 色彩 LUT 想要的
Order 1 與 Order 3
在網格點之間,讀者必須插值,而 /Order 決定方式。/Order 1 是多線性插值:單一軸線性、兩軸雙線性、三軸三線性。它快,而且正是大多數檢視器硬體做的事,對平滑色彩轉換來說通常和更花俏的方法幾乎看不出差別。/Order 3 要求三次樣條插值,會在樣本之間擬合出更平滑的曲線,但代價是更多運算,以及每個評估點周圍更寬的支撐區域
取捨在於網格密度和曲線平滑度。當網格較粗而對應帶有可見曲率時,三次階數就會發揮作用,因為兩個相距很遠的樣本之間若只用直線,會把色調曲線壓平,這在漸層上很容易被眼睛看出來。一旦網格夠密,線段已經夠短,線性插值就能很貼近曲線,三次插值帶來的好處就很少。實務上的規則是,只在網格較小或轉換很陡時才用 /Order 3,其他情況就維持線性預設值。要注意,/Order 只適用於 Type 0 函式,而 HotPDF 會拒絕除了 1 或 3 以外的任何值
3D LUT:三個輸入,三個輸出
RGB 對 RGB 色彩校正是三輸入網格的教科書式案例,也是色彩分級和裝置匹配裡最常見的 3D LUT。立方體的每條軸都是一個輸入通道,每個網格點會儲存該輸入座標的校正後 RGB 三元組,而讀者會在任何輸入顏色周圍做三線性插值。這裡三個輸入是必要的,因為校正後的紅色可能同時取決於輸入的綠色和藍色,不只是輸入的紅色;單一通道曲線無法表達通道交互作用,但立方體可以
HotPDF 透過 RegisterSampledFunction 建立 Type 0 串流,這個函式直接接受 /Domain、/Range、/Size、/BitsPerSample 和樣本位元組,然後回傳函式物件。對標準的正規化立方體,您會傳入三個輸入軸與三個輸出軸的 [0,1] 邊界、N x N x N 的大小,以及攤平的樣本表。建立器會驗證位元組數是否與網格相符:對於位元組對齊的深度,它預期的是 OutputCount x (BitsPerSample div 8) x 各尺寸乘積,如果陣列長度不對就會丟例外,所以算錯 stride 會在註冊時就明確報錯,不會拖到之後才顯示成亂資料
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 註冊界定這個轉換的設定檔,它會記錄基於 ICC 的色彩空間(1、3 或 4 個元件),並回傳一個資源名稱,您可以把它接到 LUT 所餵的內容上。Type 0 函式承載插值表,ICC 設定檔承載端點的意義
1D 案例:專色色調轉換
特別色空間用同一套機制完成完全不同的工作。定義於 ISO 32000-1 §8.6.6.4 的特別色空間,會把名稱和色調轉換配對,用來表示單一著色劑,例如 Pantone 或上光油等專色油墨。這個轉換是一個函式,會把一維色調值,也就是無油墨時為 0、滿油墨時為 1,對應到裝置實際能呈現的替代色彩空間,通常是 CMYK。這個色調轉換常常就是 Type 0 函式,這時候網格正好只有一個輸入軸
這和 3D LUT 形成鮮明對比。專色油墨只有一個自由度,所以它的色調轉換只需要一個輸入,而網格也只是一列樣本,每個樣本都存著那個色調層級下的 CMYK 或其他替代值。RGB 立方體需要三個輸入,因為它的定義域是三維的,而且通道彼此會互相影響。相同的函式類型、相同的插值規則,只是維度不同;規格書重複使用同一個評估器,然後讓 /Size 決定您是在走線、平面還是立方體。HotPDF 把整個特別色流程包在 RegisterSeparationLUT 裡,這個函式會在內部從一個扁平位元組陣列建立單輸入 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;
樣本數必須是網格點的整數倍,也就是替代空間元件數的正倍數,而且至少要有兩個點,才有可插值的線段。對 CMYK 替代空間每點傳三個位元組,呼叫就會拒絕,這和 3D 建立器套用的防禦性驗證一樣,正是您會想要的行為,因為這類函式否則會在列印時默默失敗
同一套機制還會在哪裡出現
一旦把 Type 0 看成通用的插值表,另外兩個裝置控制功能就不再像特例。轉換函式會在元件值往輸出裝置去的路上調整它們,而這本質上只是每個通道一個函式;HotPDF 透過 RegisterTransferFunctionState 把它註冊成 ExtGState,這個函式可以接受單一組合函式,或每通道函式陣列。因為這些函式都是普通的函式物件,所以您可以把 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;
黑版產生和底色去除屬於同一個家族。裝置把 RGB 轉成 CMYK 時,會決定要把多少灰階元件帶成黑色油墨,而規格書把這個決定表示成函式,也就是圖形狀態字典中的 /BG2 和 /UCR2 項目,每個都是從計算出的灰色到黑色含量的單輸入曲線。當您想要的是量測後的曲線,而不是解析式曲線時,這些也可以是 Type 0 函式,做法和前面一樣,透過 RegisterSampledFunction 建立並放進圖形狀態。值得記住的教訓是,PDF 函式從來都不是色彩管理真正發生的地方;它只是承載您透過真正色彩引擎所做決定的查找表,而 Type 0 是唯一夠彈性、足以承載任何決定的函式類型
若想看更完整的圖像,包括字型、影像和色彩資源如何被送進完成的文件,請參閱我們的字型與影像報表輸出逐步解說。當輸出必須通過封存或列印預檢,PDF/A、PDF/X 和 PDF/UA 驗證指南中說明的色彩空間與輸出目的規則,會決定哪些函式可以使用,以及裝置色彩該如何標記。這些都包含在適用於 Delphi 和 C++Builder 的 HotPDF Component 中,連同建立在同一個 Type 0 核心上的著色、ICC 與特別色 API