HotPDF dựng hình các màu Separation và DeviceN spot trên các trang PDF đã tải bằng cách phân giải không gian màu thông qua HPDFResolveColorSpace, đánh giá hàm tint-transform bằng HPDFEvalTintTransform, và chuyển đổi kết quả thông qua không gian thay thế (alternate space) sang RGB cho màn hình. Kể từ phiên bản v2.375.0, pipeline đó đánh giá cả báo loại hàm PDF, bao gồm cả các trình tính toán Type 4 PostScript, giúp một file sẵn sàng in ấn với mực Pantone hiển thị màu sắc thực tế của nó thay vì một màu giữ chỗ. Bài viết này sẽ hướng dẫn cách hoạt động của pipeline và những lỗi đã xảy ra trong quá trình chúng tôi xây dựng nó
Trường hợp kích hoạt luôn giống nhau. Một khách hàng nhận được một file PDF từ xưởng in: dòng tiêu đề được thiết kế bằng một loại mực spot được đặt tên, hình ảnh bao bì sử dụng một hỗn hợp DeviceN hai mực, và văn bản chính là màu đen thông thường. Trong Acrobat, nó trông rất hoàn hảo. Trong ứng dụng Delphi của bạn, tiêu đề hiển thị màu đen, hoặc tệ hơn là hoàn toàn không hiển thị, và khách hàng sẽ báo lỗi phần mềm của bạn thay vì báo lỗi file. File đó hoàn toàn bình thường. Chỉ là trình dựng hình không hỗ trợ không gian màu mà file đó sử dụng
Tại sao các màu spot lại hiển thị màu đen trong trình xem PDF?
Các màu spot hiển thị màu đen, hoặc biến mất, khi trình dựng hình chỉ thực thi các toán tử màu của thiết bị (rg, g, k) và bỏ qua các toán tử chung. Tiêu chuẩn ISO 32000-1 §8.6 định nghĩa ba nhóm không gian màu: không gian thiết bị (DeviceGray, DeviceRGB, DeviceCMYK), không gian dựa trên CIE (CalGray, CalRGB, Lab, ICCBased), và không gian đặc biệt (Indexed, Separation, DeviceN, Pattern). Mọi thứ nằm ngoài nhóm thiết bị đều được chọn bằng các toán tử chung: cs và CS chọn một không gian theo tên từ dictionary tài nguyên của trang, sau đó sc, SC, scn, và SCN cung cấp các giá trị thành phần. Một trình dựng hình bỏ qua các toán tử đó sẽ giữ lại bất kỳ màu nào được thiết lập gần nhất, đối với một trang có dòng tiêu đề màu spot đi đầu thì đó chính là màu đen DeviceGray ban đầu
HotPDF đã thêm bộ toán tử chung vào trình dựng hình trang của nó trong phiên bản v2.333.0, cùng với một đường dẫn phân giải thống nhất: mỗi mục tài nguyên /ColorSpace, dù là một tên trần, một mảng nội tuyến hay một tham chiếu gián tiếp, đều được phân tích thành một bản ghi THPDFColorSpace duy nhất, và mọi yêu cầu tô màu hoặc nét vẽ đều được chuyển qua một lệnh gọi HPDFResolveColor duy nhất. Danh sách enum sau đây cho thấy mức độ bao phủ
type
THPDFColorSpaceFamily = (csfDeviceGray, csfDeviceRGB, csfDeviceCMYK,
csfIndexed, csfCalGray, csfCalRGB, csfLab,
csfICCBased, csfSeparation, csfDeviceN,
csfUnsupported);
function HPDFResolveColorSpace(Obj: THPDFObject): THPDFColorSpace;
function HPDFResolveColor(const CS: THPDFColorSpace;
const Comps: THPDFColorComps; CompCount: Integer): THPDFRenderColor;
Một quyết định thiết kế đã chứng minh giá trị nhiều lần: csfUnsupported là một phân loại hợp lệ chứ không phải là một lỗi. Một không gian màu mà trình dựng hình không thể thông dịch sẽ được hạ cấp xuống một fallback xác định thay vì hủy bỏ trang, điều này khớp với cách hành xử của các trình xem phổ thông và giúp một nét vẽ lạ không làm trống toàn bộ một tài liệu lẽ ra dựng hình được
Làm thế nào một tint transform chuyển đổi một giá trị mực thành một màu thực tế?
Một không gian Separation mang ba thông tin: tên mực, một không gian màu thay thế, và một hàm tint-transform. Mảng [/Separation /PANTONE485 /DeviceCMYK f] nói rằng: khi stream nội dung viết 0.8 scn, hãy nạp giá trị tint 0.8 vào hàm f và vẽ kết quả bốn thành phần CMYK nhận được. DeviceN khái quát hóa điều này cho N mực với một hàm N đầu vào. Bản thân tên mực chỉ mang tính chất tham khảo trên màn hình; tint transform mới là toàn bộ ngữ nghĩa dựng hình, vì vậy một trình dựng hình phân tích không gian màu nhưng bỏ qua hàm thì vẫn chưa làm được gì hữu ích
HPDFEvalTintTransform là engine hàm đằng sau bước đó. Ra mắt trong phiên bản v2.334.0, nó đánh giá các hàm mũ Type 2 (C0 + x^N * (C1 - C0) với việc giới hạn Domain và Range), các hàm ghép Type 3 (đệ quy hàm con được chọn theo Bounds với ánh xạ lại Encode), và các hàm mẫu Type 0 với các mẫu 8, 16 và 32 bit. Type 0 là cùng một cơ chế bảng tra cứu (lookup-table) mà chúng tôi đã trình bày ở phía tạo file trong bài viết về xây dựng bảng tra cứu màu Type 0; phía dựng hình thực hiện duyệt qua cấu trúc giống hệt theo chiều ngược lại, từ các byte mẫu đã giải mã quay về các giá trị thành phần
Các hàm tính toán PostScript Type 4 là thành phần cuối cùng được hỗ trợ. Cho đến phiên bản v2.375.0, chúng được hạ cấp xuống một màu giữ chỗ trung tính; kể từ phiên bản v2.375.0, HPDFEvalPostScriptCalculator thực thi toàn bộ bộ toán tử của tiêu chuẩn ISO 32000-1 §7.10.5 trên một stack toán hạng có giới hạn: các toán tử số học, so sánh, boolean và bitwise, thao tác stack bao gồm roll, và các điều kiện if/ifelse. Các ngữ nghĩa góc cạnh nghiêm ngặt hơn vẻ bề ngoài. Toán tử round của PostScript làm tròn một nửa về phía giá trị lớn hơn, vì vậy hàm làm tròn của Delphi (banker's-rounding Round) không thể sử dụng; các toán tử lượng giác hoạt động bằng độ, với atan trả về giá trị trong [0, 360); và exp là lũy thừa hai toán hạng, không phải hàm mũ tự nhiên. Bộ đánh giá tương tự cũng điều khiển các tô màu gradient dựa trên hàm, đó là lý do tại sao việc dựng hình tô màu axial và radial đã bổ sung các dốc màu do trình tính toán điều khiển trong cùng một bản phát hành
// Evaluate a Separation/DeviceN tint transform (Type 0/2/3/4).
function HPDFEvalTintTransform(FuncObj: THPDFObject;
const Inputs: THPDFColorComps; InputCount: Integer;
out AltComps: THPDFColorComps): Boolean;
// Type 4 PostScript calculator, ISO 32000-1 7.10.5 operator set.
function HPDFEvalPostScriptCalculator(const Prog: TBytes;
const Inputs: THPDFColorComps; InputCount: Integer;
var Outputs: THPDFColorComps; OutCount: Integer): Boolean;
Thực tế về độ chính xác: một trình tính toán phần mềm được đánh giá ở độ chính xác kép (double precision) sẽ không khớp từng bit với một RIP, và việc giới hạn ở biên Range có thể khác biệt một bit có ý nghĩa nhỏ nhất so với triển khai khác. Đối với hiển thị màn hình và dựng hình kiểm thử thì điều đó không quan trọng; nếu bạn đang xây dựng quy trình quản lý màu sắc để in thử (proofing), tint transform chỉ là giai đoạn đầu tiên và bạn vẫn cần một CMM thực sự ở phía sau
CalGray, CalRGB, Lab, và ICCBased không cần engine ICC
Các nhóm dựa trên CIE sẽ đi theo nhánh khác của cùng một bộ phân giải. HotPDF chuyển đổi các giá trị Lab thông qua chuỗi tiêu chuẩn từ Lab sang XYZ sang sRGB, bao gồm cả khối điểm ngắt 6/29 trong hàm truyền ngược, và xử lý CalRGB với gamma trên mỗi kênh cộng với ma trận tuyến tính 3x3 và CalGray với gamma đơn. Chi tiết liên quan đến hiệu năng là việc xử lý điểm trắng: ma trận chuyển đổi từ XYZ sang sRGB được Bradford thích ứng với điểm trắng được khai báo trong không gian màu và được cache trong bản ghi THPDFColorSpace đã phân giải, do đó công việc trên mỗi pixel vẫn chỉ là một phép nhân 3x3 duy nhất cho dù illuminant được khai báo có lạ đến đâu
Các không gian ICCBased nhận được sự xử lý thực tế có chủ ý. Tiêu chuẩn PDF yêu cầu mọi stream ICCBased phải khai báo một không gian /Alternate hoặc một không gian ngụ ý thông qua số lượng thành phần /N của nó, chính xác là để các trình xem không có engine quản lý màu sắc vẫn có thể dựng hình một cách hợp lý. HotPDF phân giải ICCBased thông qua không gian thay thế đó, hoặc đoán DeviceGray, DeviceRGB, hoặc DeviceCMYK từ giá trị /N là 1, 3, hoặc 4 khi mục đó không có, và không bao giờ phân tích các byte của profile. Điều đó có nghĩa là không phụ thuộc vào lcms và không tốn chi phí tra cứu profile, đổi lại là độ chính xác về màu sắc: một không gian ICCBased có profile khác biệt mạnh mẽ so với không gian thay thế của nó sẽ hiển thị bản dựng hình thay thế. Đối với việc xem trên màn hình và ảnh thu nhỏ, đây là sự đánh đổi mà mọi trình xem dung lượng nhẹ đều thực hiện, và đó cũng là giới hạn cần nêu rõ ràng trong tài liệu của riêng bạn
Lỗi làm cho mọi tìm kiếm không gian màu có tên đều bị trượt
Bộ xử lý toán tử của phiên bản v2.333.0 đi kèm với một lỗi ẩn trong suốt bốn mươi hai bản phát hành: trình xử lý cs và CS tìm kiếm toán hạng của chúng với dấu gạch chéo dẫn đầu vẫn còn nguyên (/CS0) đối với các key dictionary tài nguyên được lưu trữ không có dấu gạch chéo (CS0). Mọi tìm kiếm không gian màu có tên đều bị trượt, một trăm phần trăm thời gian, và code đã âm thầm quay về mặc định DeviceGray. Triệu chứng hiển thị rất khó phát hiện: một tô màu Separation của 1 scn đã trở thành DeviceGray 1.0, tức là tô màu trắng, và mực trắng trên trang trắng không phải là lỗi dựng hình mà ai đó sẽ chụp màn hình báo cáo. Cách khắc phục trong phiên bản v2.375.0 là áp dụng một bộ chuẩn hóa tên chung cho mỗi lần tra cứu từ toán hạng sang tài nguyên
Hai lỗi liên quan đã được phát hiện từ cùng một cuộc điều tra. Thứ nhất, các tham chiếu gián tiếp đến các đối tượng dạng mảng được trả về mà không phân giải: quyền truy cập tài liệu của trình dựng hình chỉ có bộ phân giải có kiểu cho stream và dictionary, do đó /CS0 5 0 R trỏ đến một mảng [/Separation ...] độc lập đã trả về tham chiếu thô và không gian màu được phân tích là unsupported. Thứ hai, HPDFReadNumericArray bắt buộc ngữ nghĩa độ dài nghiêm ngặt, yêu cầu mảng PDF phải dài ít nhất bằng bộ đệm được cung cấp. Do đó, việc đọc /C0 và /C1 của một hàm Type 2 vào một bộ đệm bốn phần tử đã thất bại đối với các không gian thay thế một và ba thành phần, làm cho cả hai mảng đều bằng không, và mọi tint transform mũ phi CMYK đều dựng hình ra màu đen cho đến khi phiên bản v2.376.0 giới thiệu trình đọc khoan dung HPDFReadNumericArrayUpTo. Bài học rút ra: bất kỳ key PDF nào được tài liệu hóa là chứa một mảng số có độ dài thay đổi phải được đọc bằng một trình đọc điền-những-gì-tồn-tại, vì một bộ đệm có kích thước cố định với so khớp nghiêm ngặt sẽ biến các file hợp lệ thành các số không âm thầm
Làm thế nào để kiểm thử việc dựng hình màu spot mà không tự lừa dối mình?
Câu hỏi hóc búa là tại sao bộ test vẫn báo xanh trong suốt thời gian đó. Kiểm thử ban đầu, SeparationRendersDistinguishable, chỉ assert rằng bitmap dựng hình không hoàn toàn là màu đen. Một pipeline bỏ qua mọi màu spot về DeviceGray vẫn tạo ra đầu ra màu xám và màu trắng, vốn không phải là màu đen, do đó lệnh assert đã pass trong khi toàn bộ tính năng thực chất đã chết. Các lệnh assert yếu dạng "không trống", "không hoàn toàn đen", hoặc "mã băm khác không" không thể phân biệt một trình dựng hình hoạt động với một trình dựng hình bị hỏng, vì hầu như bất kỳ chế độ lỗi nào vẫn tạo ra một số pixel
Kiểu assert thực sự bắt được những lỗi này là khóa màu sắc mong đợi: dựng hình một file PDF tối giản được xây dựng thủ công có màu Separation phân giải thành một sắc độ đã biết, sau đó đếm các pixel có sắc độ đó chiếm ưu thế. Việc dựng hình thành một bitmap để kiểm tra sử dụng cùng một điểm đầu vào RenderLoadedPageToBitmap được mô tả trong hướng dẫn dựng hình trang thành bitmap
var
Pdf: THotPDF;
Bmp: TBitmap;
X, Y, RedHits: Integer;
Px: TColor;
begin
Pdf := THotPDF.Create(nil);
try
if Pdf.LoadFromFile('spot-red-fixture.pdf', '') > 0 then
begin
Bmp := Pdf.RenderLoadedPageToBitmap(0, 96);
try
RedHits := 0;
for Y := 0 to Bmp.Height - 1 do
for X := 0 to Bmp.Width - 1 do
begin
Px := Bmp.Canvas.Pixels[X, Y];
if (GetRValue(Px) > 180) and (GetGValue(Px) < 100) and
(GetBValue(Px) < 100) then
Inc(RedHits);
end;
// Lock the expected ink: demand a real area of red-dominant
// pixels, never settle for "not all black".
Assert(RedHits > 500);
finally
Bmp.Free;
end;
end;
finally
Pdf.Free;
end;
end;
File test (fixture) cũng quan trọng như lệnh assert. Một file PDF tự dựng chỉ nặng vài trăm byte, với một tô màu Separation duy nhất và không có gì khác, sẽ không để lại sự mơ hồ nào về đầu ra mong đợi; một file thực tế chạy nhiều code hơn nhưng không thể cho bạn biết giai đoạn nào đã thất bại. Hiện tại chúng tôi coi việc đếm pixel màu mong đợi là mức tối thiểu cho bất kỳ bài kiểm tra dựng hình nhanh (smoke test) nào, vì đó là kiểu assert duy nhất buộc những lỗi này phải lộ diện
Việc dựng hình màu spot và không gian màu CIE là một phần của quy trình tài liệu đã tải trong HotPDF Component dành cho Delphi và C++Builder, cùng với engine hàm, dựng hình shading, và các đường dẫn xuất bitmap hiển thị ở trên