以 1 0 0 rg 填色的純紅標題,經過灰階轉換後變成 0.299 g。純藍落在 0.114 g,黑到跟內文難分難辨,深藍商標與文字就此糊成一片。這些數字不是捨入的意外:它們是 BT.601 的亮度權重,跟檢視器在灰階裝置上顯示 RGB 頁面用的是同一組,也決定了彩色型錄不得不變灰階時,還能保住多少對比
RecolorDocument 對每個色彩算了什麼
PDF Library for Delphi 把這個轉換暴露為 TPDFlib 上的 RecolorDocument(TargetSpace):1 灰階、2 RGB、3 CMYK。它回傳轉換了多少東西,幾何、文字位置與圖片擺放都停在原處。變的只有色彩值與圍在周圍的色彩空間名稱
每個色彩在抵達目標之前都會先過 RGB。灰階是亮度總和 0.299 R + 0.587 G + 0.114 B。CMYK 按 ISO 32000-1 §10.3 的樸素裝置規則回到 RGB:每個色頻是一減去自身油墨與黑色之和、再夾到零。RGB 轉 CMYK 則把最大分量拉進黑:K = 1 - max(R, G, B),其餘油墨從剩下的部分縮放。送印之前有幾個結果值得知道:
- 灰階
0.4 g轉 CMYK 變成0 0 0 0.6 k,只有黑油墨,這正是印刷機想要的文字與線條 - RGB
1 0.5 0(橘色)變成0 0.5 1 0 k,完全沒有黑 0.6 0.4 0.4 1 k這種複合黑在轉灰階的路上變成0 g,多出來的油墨就這麼消失- 純青
1 0 0 0 k變成0.701 g,比多數人對飽和油墨的預期更淺
轉換後的運算元最多寫三位小數,而且一律用句點當小數點分隔符,無論執行這項工作的機器是什麼 locale。ISO 32000-1 §7.3.3 在內容串流裡不允許其他寫法,用系統 locale 格式化數字的轉換器,會在德文工作站上寫出 0,5 g,閱讀器把它解析成數字 0 加一個不認識的運算子
var
Lib: TPDFlib;
Converted: Integer;
begin
Lib := TPDFlib.Create;
try
if Lib.LoadFromFile('brochure.pdf', '') <> 1 then
raise Exception.Create('Could not open brochure.pdf');
Converted := Lib.RecolorDocument(1); // 1 = 灰階,2 = RGB,3 = CMYK
WriteLn(Format('%d colors, images and gradients converted', [Converted]));
Lib.SaveToFile('brochure-gray.pdf');
finally
Lib.Free;
end;
end;
色彩已經在目標空間裡的頁面,保留它原本的內容位元組。RecolorDocument 只統計真正跨空間的轉換,所以已經是灰階的文件回報 0,裡面什麼都不會被改寫。在大檔案上這很重要:輸入與輸出做個 diff,改變的頁面一目瞭然
為什麼 RecolorPage 留著封面照片的彩色?
RecolorPage 只轉換所選頁面上的色彩運算子,到此為止。圖片與漸層是文件層級的物件,常常被好幾頁共用,只有 RecolorDocument 會轉換它們。所以「只轉換封面」的直覺做法——選第 1 頁再呼叫 RecolorPage(3)——給您的是 CMYK 標題與線條壓在一張 RGB 照片上
這個切分是刻意的。畫在封面的圖片 XObject 可能也畫在第 12 頁,為一頁轉換它就等於為兩頁轉換。頁面範圍的呼叫若悄悄伸進共用資源,會改到呼叫端從未選過的頁面。兩種意外裡這個更糟,所以逐頁呼叫只碰屬於頁面的東西
// 只轉封面上的運算子;圖片與漸層不在這裡轉換
Lib.SelectPage(1);
Changed := Lib.RecolorPage(3);
if Changed = 0 then
WriteLn('Cover already uses CMYK operators (or the target was out of range)');
Lib.SaveToFile('cover-cmyk-operators.pdf');
// 文件必須整份同一空間時,全部轉換
Lib.RecolorDocument(3);
Lib.SaveToFile('all-cmyk.pdf');
留意那段程式碼的回傳值。目標落在 1 到 3 之外時兩個呼叫都回傳 0,而 0 也可能代表頁面本來就不用改,所以目標打錯字看起來跟文件本來就正確一模一樣。呼叫之前自己驗證引數。RecolorDocument 會還原它執行前選中的頁面,所以您可以在逐頁處理的工作中途呼叫它,事後不必重新選頁
還有一個範圍細節。運算子這一趟讀的是每頁自己的內容串流——列在頁面上的繪製指令——轉換 g、rg、k、它們的筆畫形式,以及當前空間是裝置空間時的 sc / scn。當 /DeviceRGB cs 出現在要轉換的 sc 之前,空間名稱也會一併改寫成目標。手寫轉換器若跳過這個改名,閱讀器就會在一個灰階值所在的位置繼續找三個 RGB 分量
圖片與色盤:哪些會變,哪些保住原位元組
圖片跟運算子一樣重要。型錄只轉了運算子就變灰階,灰階頁面上依舊是彩色照片,觀感比原版或完整轉換都糟。帶每元件八位元、裝置色彩空間,且無壓縮或只帶無 decode 參數的純 Flate 的圖片,RecolorDocument 會予以轉換
索引式圖片走另一條路。取樣是色彩表的索引,所以轉換的是表,每個取樣保留原本的索引值。存成 256 色盤圖片的截圖或圖表都這樣轉,取樣怎麼打包、怎麼壓縮都不影響,因為取樣從頭到尾不解碼。色盤的基底必須是裝置空間;架在 ICC 設定檔上的色盤保留原表
JPEG 或傳真編碼的圖片保住自己的位元組。改它的色彩意味著解碼、轉換、再壓縮,等於疊上第二代 JPEG 失真,還以沒人要求的方式改變檔案大小。工作真的需要每張照片都變灰階時,重新編碼是另一個有自己畫質設定的決定,我們寧可讓呼叫端明確做出這個決定,而不是由一次色彩轉換代勞
柔遮罩與鏤空遮罩保持原樣,是另一個理由。柔遮罩通常是 DeviceGray 圖片,但它的值代表的是覆蓋率。把它轉成三分量 RGB,檢視器就不再把它當透明度處理;它最後會以灰階美工圖的身分,畫在本該被它塑造的內容上面。RecolorDocument 在轉換任何圖片之前,先收集所有經 /SMask 或 /Mask 引用的物件,而帶 /ImageMask true 的圖片本來就沒有色彩可轉
運算子層級經 ICCBased、Separation、Indexed 或 Pattern 空間設定的色彩,同樣照寫入原樣保留。不讀設定檔就把特別色或帶設定檔標記的值轉成裝置灰階,會改變頁面的外觀,而一份職責是保持外觀的程序,不能開始猜測設定檔想表達什麼
漸層:色彩空間與色標一起搬
著色宣告一次色彩空間,把每個色標交給一個函式。函式還在回傳三個數字時就把 /ColorSpace 從 DeviceRGB 改名成 DeviceGray,閱讀器會拿第一個數字——紅色頻——當灰階值。藍到白的漸層於是渲染成一條從黑色開始的色帶。RecolorDocument 先以唯讀走完整棵函式樹,任何節點轉不了就什麼都不轉;全部可行,才改寫色標並改名空間
它改寫的函式是指數型——兩個端點色 /C0 與 /C1 是純陣列——以及由它們拼起來的 stitching 函式。/Background 色跟著其餘部分一起轉換。由取樣表或 PostScript 程式定義的著色保留自己的定義,網格型 4 到 7 也一樣,它們把色彩裝在串流裡而不是函式裡。改寫其中任何一種都是重新產生,跟轉換一個色彩是兩份工作
把漸層找出來本身就是個陷阱。設計工具很少給著色一個獨立物件;它通常內嵌在繪製它的圖樣字典裡,或落在頁面的 /Shading 資源中。我們打造漸層這一趟時,掃頂層物件幾乎一無所獲。現在的搜尋沿物件圖走,深度以十二層為限、每個物件編號只拜訪一次,所以全頁共用的資源只轉換一次,物件指向自身的檔案也迴圈不起來
送印之前,重新上色該擺在哪一步
把 RecolorDocument 當正規化。它在裝置空間之間轉換數值;不套用 ICC 設定檔、不理會演算用途、也不模擬印刷條件。對必須對上合約打樣的稿件,由設定檔驅動的工作流程才是正確工具,重新上色頂多是那道在工作流程啟動前、把所有東西放進同一個空間的步驟
它真正發揮價值的是日常生產場景:必須是灰階、卻帶著 RGB 商標送來的稿件;不准以彩色列印的內部報告;預檢步驟刁難到每個檔案都用同一空間才放行的一批檔案。凡是要出門的東西都該過 RecolorDocument,絕不能只跑 RecolorPage,因為轉了一半的檔案過得了快速目檢,卻會在印表機那裡翻車。判定就在同一個工作裡跑:PDF/A 與 PDF/UA 預檢與合規與簽章工作台的筆記示範怎麼做
// IsValidRecolorTarget 是您自己加的輔助函式;函式庫沒有這個呼叫
function IsValidRecolorTarget(Space: Integer): Boolean;
begin
Result := (Space >= 1) and (Space <= 3);
end;
procedure NormalizeFile(const Src, Dst: string; Space: Integer);
var
Lib: TPDFlib;
begin
if not IsValidRecolorTarget(Space) then
raise EArgumentException.CreateFmt('Bad recolor target %d', [Space]);
Lib := TPDFlib.Create;
try
if Lib.LoadFromFile(Src, '') <> 1 then
raise Exception.CreateFmt('Could not open %s', [Src]);
if Lib.RecolorDocument(Space) = 0 then
WriteLn(Src, ': already in the target space, copied unchanged');
Lib.SaveToFile(Dst);
finally
Lib.Free;
end;
end;
輸出上有個可以馬上做的檢查:灰階轉換之後,用任何 PDF 檢視器工具打開轉換頁的內容。頁面自己的內容裡不該再有任何 rg、RG、k 或 K 運算子。這一趟之後還活著的色彩來自前面列過的情況:一張 JPEG、特別色或帶設定檔的值、取樣或網格漸層,或畫在 form XObject 裡的美工圖——它的指令住在自己的串流裡,不在頁面內容中
如果您不只轉換、還要自己建構向量美工圖,向量圖形、著色器與漸層的逐步解說說明著色與圖樣最初是怎麼建出來的,讀懂它,上面的漸層規則就好預測了
色彩這一趟在行程內完成:PDF Library for Delphi 是面向 Delphi、C++Builder 與 Lazarus 的原生 Pascal 函式庫,正規化一批進來的檔案不必在伺服器上裝外部轉換器或命令列工具。其餘的文件處理 API 見 PDF Library for Delphi 產品頁