Technical Article

การเรนเดอร์การไล่ระดับสี PDF ใน Delphi: แบบแนวแกน (Axial) และแบบแนวรัศมี (Radial)

ในอดีต เมื่อเรนเดอร์ไฟล์ PDF ที่มีส่วนหัวเป็นพื้นหลังไล่สีแบบ Word หรือ Excel ให้เป็นรูปภาพ ผลลัพธ์ที่ได้มักจะเป็นสี่เหลี่ยมสีขาวว่างเปล่าในตำแหน่งที่ควรจะเป็นสีผสมกัน ปัจจุบัน ตัวเรนเดอร์หน้ากระดาษของ HotPDF รองรับตัวดำเนินการ sh (shading) ของ PDF สำหรับการไล่ระดับสีแบบแนวแกน (axial) ประเภท 2 และแบบแนวรัศมี (radial) ประเภท 3 แล้ว โดยระบบจะประมวลผลฟังก์ชันการไล่โทนสีตามแนวแกนและวาดสีผสม ในขณะที่เวอร์ชันก่อนหน้าจะข้ามตัวดำเนินการนี้ไปเงียบๆ และปล่อยให้พื้นที่นั้นเป็นสีขาวของกระดาษ การไล่โทนสีทั้งสองประเภทที่ครอบคลุมการไล่สีในเอกสารสำนักงานเกือบทั้งหมดได้รับการรองรับแล้ว ส่วนประเภทตาข่าย (mesh) ยังไม่รองรับ และบทความนี้จะอธิบายขอบเขตนั้นอย่างตรงไปตรงมา

HotPDF เป็นส่วนประกอบ VCL PDF ดั้งเดิมสำหรับ Delphi และ C++Builder และเมธอดทั้งสามนี้จะทำงานกับเอกสารที่คุณเปิดขึ้นด้วย LoadFromFile แทนที่จะสร้างขึ้นใหม่ทั้งหมด เมธอดแต่ละตัวจะรับค่าดัชนีฟิลด์แบบเริ่มต้นที่ศูนย์ (zero-based index) ในอาร์เรย์ AcroForm /Fields — ซึ่งเรียงลำดับแบบเดียวกับที่ส่งคืนจาก GetLoadedFormFieldNames — และเขียนข้อมูลลงในพจนานุกรมฟิลด์โดยตรง ดังนั้นขั้นตอนการทำงานทั้งหมดคือการโหลด, ค้นหาดัชนี, แนบสคริปต์ และ SaveLoadedDocument สำหรับการสร้างชุดฟิลด์ขึ้นมาเองนั้นจะเป็นอีกหัวข้อหนึ่งที่อธิบายไว้ใน adding AcroForm fields to a loaded PDF in Delphi ส่วนหน้านี้จะเน้นเรื่องการกำหนดกระบวนการทางคณิตศาสตร์และการจัดรูปแบบให้กับชุดฟิลด์ที่มีอยู่แล้ว

เหตุใดการไล่ระดับสี PDF จึงเรนเดอร์เป็นกล่องสีขาว?

การไล่ระดับสี PDF คือโครงสร้างเวกเตอร์ ไม่ใช่บิตแมปที่ตัวเรนเดอร์จะสามารถคัดลอกลงไปตรงๆ พจนานุกรมการไล่โทนสี (ISO 32000-1 §8.7.4.5) จะอธิบายรูปทรงเรขาคณิต — เช่น แนวแกนหรือคู่วงกลมในอาร์เรย์ Coords — และฟังก์ชันสี จากนั้นตัวดำเนินการ sh จะทำหน้าที่วาดการไล่โทนสีนั้นบนพื้นที่ตัดปัจจุบัน (clip region) ซึ่งเป็นแบบจำลองกราฟิกชุดเดียวกับที่ HotPDF แสดงผลเมื่อคุณ draw paths and fills onto a page canvas ตัวเรนเดอร์หน้ากระดาษของ HotPDF จะประมวลผลสตรีมเนื้อหาทีละตัวดำเนินการ และหากตัวดำเนินการใดไม่มีพาธโค้ดรองรับก็จะถูกข้ามไปเพื่อให้ส่วนที่เหลือของหน้ายังคงเรนเดอร์ได้ ก่อนเวอร์ชัน 2.335.0 ตัวดำเนินการ sh จะอยู่ในกลุ่มที่ถูกข้ามนี้: ทำให้การไล่ระดับสีที่ส่วนหัวหรือเต็มหน้าไม่มีผลต่อเอาต์พุต และพื้นหลังแสดงออกมาเป็นสีขาว ตัวไฟล์ไม่ได้เสียหายเลย เพียงแต่ตัวเรนเดอร์ไม่มีตัวจัดการสำหรับตัวดำเนินการนี้ และกล่องสีขาวก็คือผลลัพธ์ของการไม่มีตัวจัดการคำสั่งนั่นเอง

ห่วงโซ่การประมวลผลฟังก์ชันเบื้องหลังการไล่โทนสี

การไล่โทนสีไม่สามารถวาดได้จนกว่าจะมีการประมวลผลฟังก์ชันของมัน ดังนั้นกลไกนี้จึงต้องถูกพัฒนาขึ้นมาก่อน การไล่โทนสีแบบแนวแกนประเภท 2 และแบบแนวรัศมีประเภท 3 ต่างต้องใช้ /Function ที่ทำหน้าที่จับคู่ตำแหน่งพารามิเตอร์ตามแนวการไล่สีให้เป็นสี (ISO 32000-1 §7.10) และตัวประมวลผลฟังก์ชันของ HotPDF จะแยกแยะ FunctionType และจัดการประมวลผลตามนั้น การประมาณค่าในช่วงแบบเอกซ์โพเนนเชียลประเภท 2 (§7.10.3) จะคำนวณ C0 + t^N * (C1 - C0) ร่วมกับการจำกัดขอบเขต Domain และ Range ฟังก์ชันตัวอย่างประเภท 0 (§7.10.2) จะอ่านตารางข้อมูลตัวอย่างขนาด 8, 16 หรือ 32 บิตและเลือกค่าที่ใกล้เคียงที่สุดผ่านแผนผัง Encode ซึ่งเป็นกรณีที่อธิบายในรายละเอียดใน our walkthrough of Type 0 sampled color lookups ส่วนฟังก์ชันต่อประสานประเภท 3 (§7.10.4) จะเลือกฟังก์ชันย่อยตามอาร์เรย์ Bounds และวนซ้ำเข้าไปประมวลผล ส่วนฟังก์ชันเครื่องคำนวณ PostScript ประเภท 4 (§7.10.5) จะไม่ถูกประมวลผล — แต่จะลดระดับการทำงานลงอย่างปลอดภัยเป็นการแจ้งความล้มเหลวของการประมวลผล แทนที่จะแสดงสีที่ผิดพลาด ซึ่งสอดคล้องกับนโยบายที่รอบคอบของ HotPDF ในการทำงานของพาธเรนเดอร์ ตัวประมวลผลฟังก์ชันนี้ได้เริ่มใช้งานในเวอร์ชัน 2.334.0 ก่อนตัวดำเนินการจริงหนึ่งเวอร์ชัน เนื่องจากความถูกต้องของการวาดการไล่โทนสีจะขึ้นอยู่กับประสิทธิภาพของฟังก์ชันที่ทำงานอยู่เบื้องหลัง

HotPDF แปลงการไล่ระดับสีแบบต่อเนื่องเป็นพิกเซลอย่างไร?

HotPDF ประมาณค่าสีผสมที่ต่อเนื่องด้วยการแบ่งเป็นแถบสีพื้นจำนวนจำกัด ตัววาดภาพไล่โทนสีจะเคลื่อนไปตามแนวการไล่ระดับสี และแบ่งช่วงออกเป็นแถบสีสูงสุด 64 แถบ — หรือน้อยกว่านั้นเมื่อแนวแกนมีพิกเซลอุปกรณ์ที่สั้น — ทำการประมวลผลฟังก์ชันในแต่ละตำแหน่งพารามิเตอร์ของแถบ และถมสีรูปสี่เหลี่ยมที่ตั้งฉากกับแนวแกนด้วยพู่กันสีพื้น สิ่งที่ต้องแลกเปลี่ยนกันคือจำนวนแถบกับความเรียบเนียน: จำนวนแถบที่มากขึ้นจะช่วยให้การไล่โทนสีมีความเรียบเนียนสอดคล้องกันมากขึ้น แต่ก็ต้องแลกกับการประมวลผลการถมสีที่มากขึ้น และ 64 แถบคือขีดจำกัดสูงสุดที่ตั้งไว้ เนื่องจากจำนวนแถบนี้ยังปรับขนาดตามความยาวแนวแกนในพิกเซลอุปกรณ์ด้วย ความละเอียดของภาพจึงมีความสำคัญ การเรนเดอร์หน้าเดียวกันที่ค่า DPI ที่สูงขึ้นจะช่วยยืดแนวแกนให้ยาวขึ้น ส่งผลให้การไล่สีสั้นๆ มีแถบสีมากขึ้นและขอบแถบสีนุ่มนวลขึ้นจนกระทั่งถึงขีดจำกัด 64 แถบ

// The shading discretizer uses up to 64 color bands along the gradient
// axis, but never more than the axis is long in device pixels. Rendering
// the same page at a higher DPI lengthens that axis, so a short gradient
// gains bands and the banding softens until the 64-band ceiling is hit.
Thumb := Pdf.RenderLoadedPageToBitmap(PageIndex, 96);   // coarser bands
Proof := Pdf.RenderLoadedPageToBitmap(PageIndex, 300);  // up to 64 bands

การเรนเดอร์หน้ากระดาษที่ไล่สีเป็นบิตแมป

จุดเข้าใช้งานสาธารณะยังคงไม่เปลี่ยนแปลง: RenderLoadedPageToBitmap จะรับค่าดัชนีหน้าเริ่มต้นที่ศูนย์และค่า DPI ส่งคืนภาพบิตแมปขนาด 24 บิตที่เป็นของผู้เรียกใช้ และส่งคืน nil เมื่อเกิดข้อผิดพลาดแทนการสร้างข้อผิดพลาด (exception) ดังนั้นการรองรับการไล่สีนี้จึงเข้ากันได้กับโค้ดเดิมที่คุณเขียนไว้แล้ว หน้ารายงานที่เคยมีส่วนหัวไล่สีเป็นสีขาวว่างเปล่าในตอนนี้จะส่งคืนรูปภาพบิตแมปที่มีการไล่สีที่ถูกต้อง และไม่ต้องแก้ไข API เพื่อใช้งานเลย สิ่งหนึ่งที่ควรจำคือสิทธิ์การครอบครอง: ผู้เรียกใช้เป็นผู้ทำหน้าที่คืนหน่วยความจำ (free) ของบิตแมปที่ส่งกลับมา กระบวนการเรนเดอร์ทั้งหมด — ทั้งการตีความสตรีมเนื้อหา, รูปอักขระฝังตัว และการแคชหน้าเว็บ — ได้รับการอธิบายไว้ใน the guide to rendering PDF pages to a bitmap in Delphi

var
  Pdf: THotPDF;
  Bmp: TBitmap;
begin
  Pdf := THotPDF.Create(nil);
  try
    if Pdf.LoadFromFile('quarterly-report.pdf') > 0 then
    begin
      // Page 1 (zero-based) at 150 DPI. A gradient-filled header that
      // used to come back blank now carries its color blend.
      Bmp := Pdf.RenderLoadedPageToBitmap(0, 150);
      if Assigned(Bmp) then
      try
        Bmp.SaveToFile('page1.bmp');
      finally
        Bmp.Free;
      end;
    end;
  finally
    Pdf.Free;
  end;
end;

ตัวอย่างการตรวจสอบความถูกต้องของการประมวลผลจะใช้วิธีนี้ ทำการเรนเดอร์หน้ากระดาษ สุ่มพิกเซลที่ควรเป็นแนวการไล่ระดับสี และยืนยันว่าไม่มีสีขาวของกระดาษเหลืออยู่ — ซึ่งเป็นการตรวจสอบแบบบรรทัดเดียวที่ตรวจพบว่าการทำงานกลับเป็นแบบเดิมหรือไม่

Bmp := Pdf.RenderLoadedPageToBitmap(0, 150);
if Assigned(Bmp) then
try
  // True when the sh operator painted; False if the region fell back to white.
  HeaderPainted := Bmp.Canvas.Pixels[Bmp.Width div 2, 8] <> clWhite;
finally
  Bmp.Free;
end;

สิ่งที่ HotPDF ไม่วาดแสดงผล: การไล่โทนสีแบบตาข่าย (Mesh Shadings) และขอบตัด (Clip Edges)

การไล่โทนสีแบบตาข่ายทั้งสี่ประเภทนั้นอยู่นอกขอบเขตการทำงาน และคุณควรคำนึงถึงข้อจำกัดนี้ในเอกสารของคุณ การไล่โทนสีแบบตาข่ายสามเหลี่ยม Gouraud แบบอิสระ (ประเภท 4), ตาข่าย Gouraud แบบตารางประสาน (ประเภท 5), ตาข่ายผิวโค้ง Coons (ประเภท 6) และตาข่ายผิวโค้งผลคูณเทนเซอร์ (ประเภท 7) ตามที่กำหนดใน ISO 32000-1 §8.7.4.5.5 ถึง §8.7.4.5.8 จะไม่ได้รับการเรนเดอร์ แต่หน้ากระดาษที่ใช้งานตาข่ายประเภทนี้จะยังคงเรนเดอร์ส่วนอื่นได้ตามปกติ เพียงแต่พื้นที่ดังกล่าวจะคงสภาพเดิมไว้ นอกจากนี้ยังมีข้อจำกัดอีกสองประการที่ควรชี้แจงอย่างตรงไปตรงมา รูปสี่เหลี่ยมผืนผ้าของแถบสีจะครอบคลุมพื้นที่การไล่สีทั้งหมดแทนที่จะถูกตัดขอบพอดีตามเส้นทางขอบเขตตัด (clip path) ปัจจุบัน ดังนั้นการไล่สีที่ถูกปิดบัง (mask) ในรูปทรงกลมหรือรูปทรงอิสระจะวาดภาพเป็นขอบเขตแถบสี่เหลี่ยมจนกว่าระบบการตัดขอบที่แม่นยำจะได้รับการพัฒนาในเวอร์ชันถัดไป อีกทั้ง HotPDF จะถือว่าเอาต์พุตของฟังก์ชันเป็น RGB หรือระดับสีเทา (grayscale) เสมอ ดังนั้นการไล่โทนสีที่สร้างขึ้นในระบบสี CMYK หรือระบบสีพิเศษอื่นจะถูกประมาณค่าแทนที่จะมีการจัดการสีที่ถูกต้องแม่นยำ สำหรับเอกสารที่ทีมพัฒนา Delphi และ C++Builder ส่วนใหญ่ประมวลผลจริง — เช่น รายงาน, ใบแจ้งหนี้, ใบเสร็จ และสไลด์นำเสนอที่ส่งออกเป็น PDF — การไล่สีแบบแนวแกนและแนวรัศมีจะครอบคลุมเกือบทุกรูปแบบการไล่สีที่คุณพบเจอ และหน้างานออกแบบที่มีการไล่สีแบบตาข่ายหนาแน่นควรได้รับการจัดการในลักษณะของภาพตัวอย่างมากกว่าภาพต้นฉบับจริง

ตัวเรนเดอร์หน้ากระดาษของ HotPDF, การรองรับตัวดำเนินการ sh ที่อธิบายในหน้านี้ และตัวประมวลผลฟังก์ชันประเภท 0/2/3 ที่ทำงานอยู่เบื้องหลัง ได้รับการจัดส่งมาพร้อมกับ HotPDF Component สำหรับ Delphi และ C++Builder — ซึ่งเป็นไลบรารี VCL ดั้งเดิมที่ไม่มีการพึ่งพาไฟล์ DLL ภายนอก ครอบคลุมทั้งการสร้าง, แก้ไข, ดึงข้อความ และการเรนเดอร์หน้าเอกสาร PDF ในแพ็กเกจเดียว