Technical Article

3D Color LUT ใน PDF พร้อมฟังก์ชันตัวอย่างแบบ Type 0

ฟังก์ชัน PDF (PDF Functions) เป็นหนึ่งในส่วนที่เงียบเชียบที่สุดในข้อกำหนดของรูปแบบไฟล์ นักพัฒนาส่วนใหญ่จะพบกับพวกมันเพียงครั้งเดียวในฐานะเครื่องมือที่การแรเงาตามแนวแกนแบบที่ 2 (Type 2 axial shading) ต้องการเพื่อสร้างเอฟเฟกต์สีค่อยๆ จางระหว่างสองสี จากนั้นก็ไม่ได้หันมามองมันอีกเลย น่าเสียดายเพราะกลไกฟังก์ชันเป็นเครื่องประเมินผลอเนกประสงค์ขนาดเล็กที่รูปแบบไฟล์นำกลับมาใช้ใหม่สำหรับการแรเงา ฟังก์ชันการส่งผ่าน ฟังก์ชันจุดฮาล์ฟโทน สีระดับแยกส่วน และเส้นโค้งการส่งผ่านซอฟต์มาสก์ ในบรรดาฟังก์ชันทั้งสี่ประเภทฟังก์ชัน Type 0 ทรงพลังที่สุดและเข้าใจได้ยากที่สุด มันคือฟังก์ชันตัวอย่าง (sampled function) ซึ่งก็คือกริดข้อกำหนดหลายมิติของค่าเอาต์พุตที่โปรแกรมอ่านจะสอดแทรกสีระหว่างนั้น เนื่องจากกริดสามารถเก็บตัวเลขใดๆ ที่คุณใส่ลงไปได้ ฟังก์ชัน Type 0 จึงสามารถแสดงความสอดคล้องแบบไม่เชิงเส้นใดๆ ได้ ซึ่งเป็นรูปแบบที่แท้จริงของตารางค้นหาสี (color lookup table)

บทความนี้จะพาเดินดูพจนานุกรม Type 0 ตามที่มาตรฐาน ISO 32000-1 กำหนดไว้ในหัวข้อ §7.10.2 จากนั้นแสดงให้เห็นสองกรณีการใช้งานที่มีความสำคัญมากที่สุดในขั้นตอนการประมวลผลเอกสาร: ตารางแปลงสีแบบสามอินพุต RGB-to-RGB สำหรับปรับแต่งสี และตัวแปลงระดับสีพิเศษแบบหนึ่งอินพุต ตัวสร้างฟังก์ชันตัวอย่างตัวเดียวกันรองรับทั้งสองกรณี โดยความแตกต่างระหว่างพวกมันคือจำนวนอินพุตที่กริดมีอยู่

ฟังก์ชันตัวอย่างคือกริดที่โปรแกรมอ่านใช้สอดแทรกค่า

ฟังก์ชัน Type 0 จะจับคู่เวกเตอร์อินพุตจำนวน m เข้ากับเวกเตอร์เอาต์พุตจำนวน n โดยการจัดเก็บตัวอย่างบนกริดปกติและทำการสอดแทรกค่าระหว่างจุดเหล่านั้น มาตรฐาน ISO 32000-1 §7.10.2 แสดงรายการคีย์ที่อธิบายกริดนั้น คีย์ /Domain เก็บตัวเลขสองตัวต่ออินพุต ซึ่งเป็นขอบเขตต่ำและสูงของแต่ละแกนอินพุต คีย์ /Range เก็บตัวเลขสองตัวต่อส่วนประกอบเอาต์พุต คีย์ /Size คืออาร์เรย์ของจำนวนเต็ม m ตัวที่ระบุจำนวนตัวอย่างตามแกนอินพุตแต่ละแกน ดังนั้นกริดที่มีตัวอย่างสิบสองตัวอย่างในแต่ละด้านในสามมิติจะมีค่า /Size [12 12 12] และจัดเก็บจุดกริด 1,728 จุด คีย์ /BitsPerSample กำหนดความแม่นยำของแต่ละค่าที่จัดเก็บ โดย HotPDF ยอมรับค่า 1, 2, 4, 8, 12, 16, 24 และ 32 บิต ซึ่งสอดคล้องกับค่าที่ตารางที่ 38 อนุญาต

กระแสข้อมูลตัวอย่างจะถูกอ่านตามลำดับที่แน่นอน มิติอินพุตแรกจะเปลี่ยนแปลงเร็วที่สุด ตามด้วยมิติที่สอง และตามลำดับขั้นไปเรื่อยๆ และในแต่ละจุดกริดจะจัดเก็บส่วนประกอบเอาต์พุตจำนวน n ตามลำดับ สำหรับตารางการแปลงสี RGB-to-RGB จะมีขนาดสามไบต์ต่อจุดกริดที่ความละเอียดแปดบิต โดยจัดเรียงเอาต์พุตสีแดง เอาต์พุตสีเขียว เอาต์พุตสีน้ำเงิน โดยจะเริ่มประมวลผลผ่านอินพุตสีแดงก่อน คีย์เพิ่มเติมอีกสองตัวจะทำหน้าที่แมปโลกที่ต่อเนื่องเข้ากับกริดจำนวนเต็ม คีย์ /Encode แมปอินพุตแต่ละตัวจากช่วงของ /Domain ไปยังช่วงดัชนีตัวอย่าง 0 ถึง Size[i] - 1 และคีย์ /Decode แมปจำนวนเต็มดิบที่จัดเก็บไว้กลับไปยังช่วงของ /Range เมื่อคุณปล่อยให้พารามิเตอร์เหล่านี้ใช้ค่าเริ่มต้น อินพุตที่มีช่วง [0 1] จะจับคู่เข้ากับกริดที่สมบูรณ์ได้อย่างสะอาด และไบต์ที่เก็บข้อมูลไว้ 255 จะถูกถอดรหัสออกมาเป็นค่าสูงสุดของช่วงเอาต์พุต ซึ่งตรงกับที่ตารางการปรับแต่งค่าสีมาตรฐาน [0,1] ต้องการ

Order 1 แข่งกับ Order 3

ระหว่างจุดกริดต่างๆ โปรแกรมอ่านจำเป็นต้องสอดแทรกค่า และคีย์ /Order จะเลือกวิธีการ คีย์ /Order 1 คือการสอดแทรกแบบหลายมิติเชิงเส้น (multilinear interpolation): เชิงเส้นตามแกนเดียว สองแกนเป็นแบบทวิเชิงเส้น (bilinear) และสามแกนเป็นแบบไตรเชิงเส้น (trilinear) วิธีนี้ทำงานเร็วและเป็นสิ่งที่ฮาร์ดแวร์ของโปรแกรมอ่านส่วนใหญ่ทำได้ และสำหรับการแปลงสีที่ราบรื่นนั้น โดยทั่วไปจะให้ผลลัพธ์ที่ไม่ต่างจากการคำนวณที่ซับซ้อนกว่า คีย์ /Order 3 จะเรียกใช้การสอดแทรกแบบลูกบาศก์สปไลน์ (cubic-spline interpolation) ซึ่งจะพอดีกับเส้นโค้งที่เรียบเนียนผ่านตัวอย่างโดยแลกกับการคำนวณที่มากขึ้นและพื้นที่สนับสนุนที่กว้างขึ้นรอบๆ แต่ละจุดที่ถูกประเมินผล

ข้อแลกเปลี่ยนคือความหนาแน่นของกริดกับความเรียบเนียนของเส้นโค้ง การสอดแทรกแบบลูกบาศก์จะมีประโยชน์เมื่อกริดมีลักษณะหยาบและการจับคู่มีส่วนโค้งที่มองเห็นได้ชัดเจน เนื่องจากเส้นตรงระหว่างตัวอย่างสองตัวที่ห่างกันสามารถทำให้เส้นโค้งโทนสีแบนลงในลักษณะที่ดวงตาของมนุษย์สามารถตรวจจับได้บนพื้นที่ไล่ระดับสี เมื่อกริดมีความหนาแน่น ส่วนของเส้นจะมีขนาดสั้นลงพอที่การสอดแทรกเชิงเส้นจะติดตามเส้นโค้งได้อย่างใกล้ชิดและไม่จำเป็นต้องใช้สปไลน์ลูกบาศก์ กฎในทางปฏิบัติคือเลือกใช้ /Order 3 เฉพาะกับกริดขนาดเล็กหรือการแปลงค่าที่ชันมากๆ เท่านั้น และปล่อยให้เป็นค่าเริ่มต้นเชิงเส้นในกรณีอื่น โปรดทราบว่า /Order ใช้กับฟังก์ชัน Type 0 เท่านั้น และ HotPDF จะปฏิเสธค่าอื่นใดที่ไม่ใช่ 1 หรือ 3

3D LUT: สามอินพุต สามเอาต์พุต

การปรับแต่งสี RGB-to-RGB เป็นตัวอย่างคลาสสิกสำหรับกริดแบบสามอินพุต ซึ่งก็คือ 3D LUT แบบดั้งเดิมที่ใช้ในการจัดเกรดสีและการจับคู่อุปกรณ์ แต่ละแกนของลูกบาศก์คือหนึ่งช่องทางอินพุต จุดกริดแต่ละจุดจะเก็บชุดสี RGB สามตัวที่ได้รับการแก้ไขแล้วสำหรับพิกัดอินพุตนั้น และโปรแกรมอ่านจะสอดแทรกค่ามุมแบบไตรเชิงเส้นรอบๆ สีที่ส่งเข้ามา อินพุตสามตัวเป็นสิ่งที่ไม่สามารถหลีกเลี่ยงได้ที่นี่เนื่องจากสีแดงที่ได้รับการแก้ไขแล้วอาจขึ้นอยู่กับอินพุตสีเขียวและสีน้ำเงิน ไม่ใช่แค่เพียงอินพุตสีแดงเท่านั้น เส้นโค้งแยกตามช่องสัญญาณไม่สามารถแสดงสัญญาณรบกวนข้ามช่องสัญญาณได้ แต่โครงสร้างลูกบาศก์สามารถทำได้

HotPDF สร้างกระแสข้อมูล Type 0 ผ่านฟังก์ชัน RegisterSampledFunction ซึ่งจะรับค่า /Domain, /Range, /Size, /BitsPerSample และไบต์ข้อมูลตัวอย่างโดยตรงแล้วส่งกลับวัตถุฟังก์ชัน สำหรับลูกบาศก์มาตรฐานที่ปรับให้เป็นค่ามาตรฐาน คุณจะต้องส่งขอบเขต [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 ซึ่งเป็นกลไกเดียวกับการพิสูดธ์อักษรบนหน้าจอ (soft-proof) เพื่อให้ตัวเลขในกริดมีความหมายสอดคล้องกับโปรไฟล์ต้นทางและปลายทางที่กำหนดไว้ ลงทะเบียนโปรไฟล์ที่ระบุขอบเขตการแปลงด้วย RegisterICCProfile ซึ่งจะบันทึกปริภูมิสีแบบอิงตาม ICC (ICCBased) (ส่วนประกอบ 1, 3 หรือ 4 ตัว) และส่งคืนชื่อทรัพยากรที่คุณสามารถนำไปแนบกับเนื้อหาที่ LUT ส่งข้อมูลให้ ฟังก์ชัน Type 0 จะทำหน้าที่เก็บตารางการสอดแทรก ส่วนโปรไฟล์ ICC จะอธิบายความหมายของขอบเขตสีปลายทางเหล่านั้น

กรณี 1 มิติ: การแปลงระดับสีพิเศษ

ปริภูมิสีแบบแยกสี (Separation color spaces) จะพึ่งพากลไกเดียวกันนี้ในการทำงานที่แตกต่างกันอย่างสิ้นเชิง ปริภูมิแบบแยกสีที่กำหนดไว้ใน ISO 32000-1 §8.6.6.4 จะแทนค่าสารสีตัวเดียว เช่น หมึกพิมพ์สีพิเศษ (spot ink) ของ Pantone หรือน้ำยาเคลือบเงา โดยจับคู่ชื่อสีเข้ากับการแปลงระดับสี (tint transform): ซึ่งเป็นฟังก์ชันที่แปลงค่าระดับสีแบบหนึ่งมิติ ตั้งแต่ 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 เป็นตารางการสอดแทรกทั่วไป คุณสมบัติการควบคุมอุปกรณ์อีกสองส่วนก็จะไม่ดูเหมือนเป็นกรณีพิเศษอีกต่อไป ฟังก์ชันการส่งผ่าน (transfer function) จะปรับค่าส่วนประกอบในขณะส่งข้อมูลไปยังอุปกรณ์เอาต์พุต และเป็นเพียงฟังก์ชันแยกตามแต่ละช่องสัญญาณ HotPDF จะลงทะเบียนในรูป ExtGState ผ่านฟังก์ชัน RegisterTransferFunctionState ซึ่งยอมรับฟังก์ชันแบบรวมตัวเดียวหรืออาร์เรย์ของฟังก์ชันแยกตามช่องสัญญาณ เนื่องจากฟังก์ชันเหล่านั้นเป็นวัตถุฟังก์ชันปกติ คุณจึงสามารถส่ง THPDFStreamObject ที่ได้จาก RegisterSampledFunction เพื่อขับเคลื่อนเส้นโค้งการส่งผ่านจากตารางตัวอย่างแทนที่จะใช้สูตรคำนวณ

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 ของพจนานุกรมสถานะกราฟิก โดยแต่ละรายการเป็นเส้นโค้งหนึ่งอินพุตตั้งแต่ค่าสีเทาที่คำนวณได้ไปจนถึงปริมาณสีดำ ฟังก์ชันเหล่านี้คือฟังก์ชัน Type 0 เช่นกันเมื่อคุณต้องการเส้นโค้งที่วัดได้จริงแทนที่จะเป็นสูตรวิเคราะห์ โดยสร้างด้วยวิธีเดียวกันผ่าน RegisterSampledFunction และจัดวางไว้ในสถานะกราฟิก บทเรียนที่ควรจดจำคือฟังก์ชัน PDF ไม่ใช่จุดที่จัดการสีสัน แต่มันคือตารางค้นหาข้อมูลที่เก็บการตัดสินใจที่คุณทำกับเอ็นจินสีจริง และ Type 0 เป็นฟังก์ชันประเภทเดียวที่มีความยืดหยุ่นมากพอที่จะเก็บผลการตัดสินใจใดๆ ได้ทั้งหมด

สำหรับภาพกว้างของวิธีที่ฟอนต์ รูปภาพ และทรัพยากรสีถูกส่งออกไปยังเอกสารที่เสร็จสมบูรณ์ โปรดดูคู่มือการแสดงผลรายงานด้วยฟอนต์และรูปภาพของเรา เมื่อเอาต์พุตต้องผ่านการตรวจสอบความถูกต้องก่อนการจัดเก็บถาวรหรือการพิมพ์ กฎปริภูมิสีและกฎเป้าหมายเอาต์พุตที่ครอบคลุมอยู่ในคู่มือการตรวจสอบความถูกต้องของ PDF/A, PDF/X และ PDF/UA จะควบคุมว่าฟังก์ชันใดที่สามารถใช้งานได้และต้องแท็กสีอุปกรณ์อย่างไร ทั้งหมดนี้มีจัดส่งให้ในโครงสร้าง HotPDF Component สำหรับ Delphi และ C++Builder พร้อมด้วย API การแรเงา, ICC และ API การแยกสีที่ทำงานอยู่บนหลักการทำงานของฟังก์ชัน Type 0 เดียวกัน