Technical Article

การบีบอัดรูปภาพแบบสองระดับ JBIG2 แบบเนทีฟในเอกสาร PDF ของ Delphi

สัญญาที่สแกนมาคือหมึกสีดำบนกระดาษสีขาวที่มีความละเอียดไม่กี่ร้อยจุดต่อนิ้ว เมื่อถูกจัดเก็บในรูปแบบบิตแมปแบบหนึ่งบิตต่อพิกเซลมันก็มีขนาดเล็กอยู่แล้ว แต่หน้าแบบนี้หนึ่งร้อยหน้าก็ยังทำให้ PDF มีขนาดใหญ่เกินกว่าที่คุณจะส่งอีเมลได้ ตัวกรองที่ถูกต้องจะเปลี่ยนวิธีการคำนวณ JBIG2 เป็นการบีบอัดที่มีอัตราส่วนสูงสุดที่ ISO 32000-1 กำหนดไว้สำหรับรูปภาพแบบสองระดับ (bilevel) และสำหรับกลุ่มของข้อความที่สแกนมา โดยปกติแล้วมันจะลดขนาดลงเหลือครึ่งหนึ่งของสิ่งที่ CCITT Group 4 สร้างขึ้น นี่คือตัวกรองที่คุณควรนึกถึงเมื่อข้อมูลนำเข้าถูกส่งแฟกซ์ สแกน หรือถูกลดให้เหลือเพียงสองสี และ HotPDF สามารถเขียนตัวกรองนี้ลงใน PDF ได้โดยตรง

รูปแบบนี้ได้อัตราส่วนดังกล่าวมาด้วยแนวคิดสองประการที่ตัวเข้ารหัสและถอดรหัสรูปภาพทั่วไปไม่มี แนวคิดแรกคือมันสร้างแบบจำลองว่าสีดำที่ต่อเนื่องกันจะวางตัวอย่างไรบนพื้นหลังสีขาว และแนวคิดที่สองคือมันสังเกตว่าหน้าเอกสารที่สแกนมานั้นส่วนใหญ่ก็คือรูปทรงอักขระ (glyph) ซ้ำๆ กันไม่กี่ร้อยแบบที่ถูกใช้ซ้ำนับพันครั้ง การเข้าใจทั้งสองสิ่งนี้คือสิ่งที่ช่วยให้คุณสามารถเลือกตัวเลือกการเข้ารหัสได้อย่างตั้งใจแทนที่จะต้องเดา

ตำแหน่งของ JBIG2 ในข้อกำหนดเฉพาะของ PDF

ISO 32000-1 แสดง JBIG2Decode ไว้ในตัวกรองสตรีมใน §7.4.7 ซึ่งมีให้ใช้งานตั้งแต่ PDF 1.4 เป็นต้นไป มันถูกนำไปใช้ในที่เดียวเท่านั้น คือออบเจ็กต์รูปภาพ XObject ที่ /BitsPerComponent มีค่าเป็น 1 และปริภูมิสีแยกวิเคราะห์เป็นแชนเนลเดียว นั่นคือประเด็นสำคัญ JBIG2 เป็นตัวแปลงสัญญาณแบบสองระดับ ดังนั้นมันจึงไม่เคยไปแข่งขันกับ DCT หรือ JPXDecode ในเรื่องของภาพถ่าย แต่มันจะไปแข่งขันกับ CCITTFaxDecode ซึ่งเป็นตัวกรองแฟกซ์ Group 3 และ Group 4 ในหน้ากระดาษแบบสองโทนสีที่เครื่องสแกนเอกสารสร้างขึ้นได้อย่างแม่นยำ

ตัวถอดรหัสจะใช้การจัดการโครงสร้าง JBIG2 ที่ฝังอยู่ซึ่งมาตรฐานเรียกว่าโปรไฟล์ PDF โดยสตรีมรูปภาพแต่ละอันจะมีลำดับของส่วนย่อย (segment) แทนที่จะเป็นแค่บิตสตรีมเปล่าๆ สตรีม /JBIG2Globals ที่เป็นทางเลือกจะนำพาส่วนย่อยที่ใช้ร่วมกันระหว่างรูปภาพหลายรูปในเอกสารเดียวกัน ซึ่งเป็นกลไกที่ช่วยให้สามารถจัดเก็บเนื้อหาที่ซ้ำกันเพียงครั้งเดียวสำหรับทั้งไฟล์แทนที่จะเป็นหนึ่งครั้งต่อหน้า HotPDF จะปล่อยสตรีมต่อรูปภาพตามค่าเริ่มต้นและปล่อยให้แชนเนล globals ว่างไว้ เว้นแต่ว่าแบ็กเอนด์จะร้องขอมา

สถาปัตยกรรมตัวเข้ารหัสแบบแบ็กเอนด์ต้องมาก่อน (backend-first)

ตัวเข้ารหัส JBIG2 ที่สมบูรณ์แบบคือซอฟต์แวร์ขนาดใหญ่ชิ้นหนึ่ง และในอดีตส่วนที่ซับซ้อนที่สุดของมันมักจะติดปัญหาเรื่องสิทธิบัตรและจัดจำหน่ายภายใต้ใบอนุญาตที่ไม่เหมาะสมกับผลิตภัณฑ์ทุกประเภท HotPDF ได้แก้ปัญหาความตึงเครียดนั้นด้วยการแยกส่วนติดต่อผู้ใช้ (interface) ออกจากเอนจิน ยูนิต HPDFJBIG2 กำหนดการเรียกใช้งานที่ส่วนอื่นๆ ของไลบรารีใช้ และมันมาพร้อมกับตัวเข้ารหัสในตัวที่เรียบง่ายเพื่อให้ JBIG2 ทำงานได้ทันที เมื่อคุณต้องการอัตราส่วนระดับโปรดักชัน คุณสามารถลงทะเบียนเอนจินที่มีประสิทธิภาพสูงกว่าและไลบรารีจะมอบหมายงานให้มัน โดยไม่ต้องเปลี่ยนแปลงโค้ดที่คุณใช้เรียกเลย

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

uses
  HPDFJBIG2;

// Query what is active, then optionally install a stronger engine.
if not IsJBIG2EncoderBackendAvailable then
  // Production backend not present: HotPDF uses its built-in MMR path.
  RegisterJBIG2EncoderBackend(MyVendorJBIG2Encode);

// Later, to return to the built-in behaviour:
// ClearJBIG2Backends;

The same hook exists for decoding through RegisterJBIG2DecoderBackend, with IsJBIG2DecoderBackendAvailable to probe it. This is why a library ships a small built-in path plus a backend seam rather than one monolithic encoder. The built-in path keeps the binary lean and free of license entanglements, while the seam lets a team that has licensed a full encoder plug it in without touching the PDF-writing layer at all

ตัวเลือกการเข้ารหัสจริงๆ แล้วคือการแลกเปลี่ยนกับอะไร

การเข้ารหัสถูกกำหนดค่าผ่าน TJBIG2EncodeOptions ซึ่งเป็นเรคคอร์ดที่มีฟิลด์ Lossless, UseGlobalSegments, UseSymbolDictionary และ LossyLevel แรปเปอร์ที่เป็นมิตรกับคอมโพเนนต์อย่าง THPDFJBIG2Options จะเผยแพร่ Lossless, UseSymbolDictionary และ LossyLevel เพื่อให้สามารถตั้งค่าได้จาก Object Inspector และมันจะแปลงเป็นเรคคอร์ดภายใน มีความตั้งใจสามประการที่ใช้ขับเคลื่อนการตั้งค่าเหล่านี้

การสร้างใหม่แบบไม่สูญเสียข้อมูล (Lossless) จะรักษาทุกพิกเซลเอาไว้ หากตั้งค่า Lossless เป็น True และปล่อยให้ LossyLevel เป็นศูนย์ บิตแมปที่ถูกถอดรหัสออกมาจะเหมือนกับบิตแมปนำเข้าทุกประการ (bit-for-bit) นี่เป็นตัวเลือกที่ปลอดภัยเพียงอย่างเดียวสำหรับภาพลายเส้น แบบแปลนทางเทคนิค และหน้าใดๆ ที่การตกหล่นของพิกเซลอาจทำให้ความหมายเปลี่ยนไป เช่น ลายเซ็น หรือตรายาง การเข้ารหัสด้วยพจนานุกรมสัญลักษณ์จะเปิดใช้งานการลบข้อมูลซ้ำซ้อนโดยรับรู้ถึงข้อความ และเป็นตัวเลือกที่ทำให้ JBIG2 แตกต่างจากตัวกรองแฟกซ์ ระดับการสูญเสียข้อมูล (lossy level) ซึ่งเป็นจำนวนเต็มตั้งแต่ 0 ถึง 9 จะช่วยให้แบ็กเอนด์ที่มีความสามารถสามารถแลกเปลี่ยนความเที่ยงตรงกับขนาดได้โดยการจัดการกับเครื่องหมายที่เกือบจะเหมือนกันให้เป็นสัญลักษณ์เดียวกัน ศูนย์หมายถึงไม่สูญเสียข้อมูล ตัวเข้ารหัสที่มาพร้อมในตัวจะรองรับเฉพาะเส้นทางที่ไม่สูญเสียข้อมูลเท่านั้น และจะเพิกเฉยต่อระดับการสูญเสียข้อมูลที่ไม่ใช่ศูนย์ ดังนั้นระดับที่สูงกว่าจะมีผลก็ต่อเมื่อมีการลงทะเบียนแบ็กเอนด์ที่รองรับคุณสมบัตินี้แล้วเท่านั้น

var
  Options: TJBIG2EncodeOptions;
begin
  Options := DefaultJBIG2EncodeOptions;   // Lossless True, symbol dictionary on
  Options.Lossless := True;
  Options.LossyLevel := 0;                // 0 keeps every pixel
  Options.UseSymbolDictionary := True;    // dedupe repeated glyphs
  // Pass Options to a backend, or let THPDFJBIG2Options carry them.
end;

พจนานุกรมสัญลักษณ์และเหตุผลที่การสแกนข้อความถึงชนะ

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

นี่คือจุดที่ JBIG2 นำหน้า CCITT Group 4 อย่างชัดเจน Group 4 จะเข้ารหัสบรรทัดสแกนแต่ละบรรทัดเทียบกับบรรทัดที่อยู่ด้านบนโดยไม่มีแนวคิดเรื่องรูปทรงอักขระ (glyph) ดังนั้นมันจึงต้องจ่ายต้นทุนเต็มจำนวนของตัวอักษรทุกตัวทุกครั้งที่ตัวอักษรนั้นปรากฏขึ้น แต่ JBIG2 จ่ายเพียงครั้งเดียว เมื่อพจนานุกรมเดียวกันนี้ถูกเลื่อนระดับขึ้นไปเป็นสตรีมส่วนกลาง (globals stream) ระดับเอกสาร การประหยัดนี้ก็จะทวีคูณขึ้นไปตลอดการสแกนแบบหลายหน้า เนื่องจากรูปทรงต่างๆ ที่ถูกใช้ร่วมกันหน้าแล้วหน้าเล่าจะถูกจัดเก็บเพียงครั้งเดียวสำหรับทั้งไฟล์ ในข้อความที่มีความหนาแน่นสูง ความแตกต่างนั้นไม่ได้เล็กน้อยเลย นี่แหละคือเหตุผลที่ JBIG2 ถึงมีอยู่

ขอบเขตทั่วไปและ MMR สำหรับสิ่งอื่นๆ ทั้งหมด

ไม่ใช่ว่ารูปภาพแบบสองระดับทุกภาพจะเป็นข้อความ แผนที่ แบบร่าง แบบแปลนวิศวกรรม และหน้าที่มีการผสมผสานกัน ล้วนมีภาพลายเส้นที่ไม่มีพจนานุกรมใดสามารถสรุปได้ สำหรับกรณีเหล่านั้น JBIG2 จะเข้ารหัสขอบเขตทั่วไป (generic region) ซึ่งเป็นพื้นที่สี่เหลี่ยมของพิกเซลที่ถูกบีบอัดโดยตรงโดยไม่ต้องมีการฝึกอบรมสัญลักษณ์ มาตรฐานอนุญาตให้ขอบเขตทั่วไปสามารถใช้ MMR หรือการเข้ารหัสแบบ modified modified READ ซึ่งแฟกซ์ Group 4 ใช้อยู่แล้ว โดยจะจำลองแถวของพิกเซลแต่ละแถวเทียบกับแถวที่อยู่ด้านบน

นี่คือเส้นทางที่ HotPDF ให้มาพร้อมกับตัวเข้ารหัสในตัว เมื่อไม่มีการลงทะเบียนแบ็กเอนด์และคำร้องขอเป็นแบบไม่สูญเสียข้อมูล (lossless) ไลบรารีจะบีบอัดบิตแมปให้เป็นขอบเขตทั่วไปของ MMR เพียงตัวเดียว และห่อหุ้มมันไว้ในโครงสร้างส่วนย่อย JBIG2 ที่โปรไฟล์ PDF ต้องการ มันไม่ต้องการพจนานุกรม ไม่ต้องมีการผ่านกระบวนการฝึกอบรม และไม่ต้องใช้รูปภาพที่สองมาอ้างอิง ดังนั้นมันจึงเป็นค่าเริ่มต้นที่พึ่งพาได้สำหรับภาพลายเส้นและเนื้อหาแบบสองระดับที่ผสมผสานกัน มันอาจเทียบไม่ได้กับตัวเข้ารหัสพจนานุกรมสัญลักษณ์เต็มรูปแบบในกรณีที่เป็นข้อความล้วนๆ แต่มันก็ถูกต้องเสมอ ไม่สูญเสียข้อมูลเสมอ และมีอยู่เสมอ พื้นผิวตัวเข้ารหัสสำหรับสิ่งนี้คือการเรียกใช้งานเพียงครั้งเดียว

var
  Encoder: THPDFJBIG2Encoder;
  ImageData: TJBIG2ByteArray;
  Scanlines: TJBIG2ScanlineArray;  // one byte array per row, MSB-first
  W, H: Integer;
begin
  // Scanlines, W and H describe a 1-bit page; each row is (W + 7) div 8 bytes.
  Encoder := THPDFJBIG2Encoder.Create;
  try
    if Encoder.EncodeToByteArray(Scanlines, W, H, ImageData) then
      // ImageData now holds a JBIG2 stream ready for a /JBIG2Decode XObject.
      ;
  finally
    Encoder.Free;
  end;
end;

การเปิดใช้งานเมื่อคุณสร้างเอกสาร

สำหรับการใช้งานในชีวิตประจำวัน คุณไม่ต้องไปยุ่งกับคลาสตัวเข้ารหัสโดยตรง HotPDF นำเสนอ JBIG2 เป็นทางเลือกการบีบอัดรูปภาพในเอกสาร การแจงนับ (enumeration) THPDFImageCompressionType รวมถึง icJBIG2 จะอยู่เคียงข้างตัวเลือกอย่าง Flate, JPEG และ CCITT และเอกสารก็จะมีคุณสมบัติ JBIG2Options ของชนิด THPDFJBIG2Options ซึ่งใช้เก็บการตั้งค่าที่ใช้งานเมื่อเลือกการบีบอัดนั้น กำหนดค่าทั้งสองอย่างนี้ก่อนที่คุณจะเพิ่มรูปภาพแบบสองระดับที่คุณต้องการบีบอัดด้วยวิธีนี้

var
  Pdf: THotPDF;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.ImageCompressionType := icJBIG2;     // route 1-bit images through JBIG2
    Pdf.JBIG2Options.Lossless := True;        // keep every pixel
    Pdf.JBIG2Options.UseSymbolDictionary := True;
    Pdf.JBIG2Options.LossyLevel := 0;
    // Add pages and place your scanned 1-bit images here.
  finally
    Pdf.Free;
  end;
end;

ความสะดวกสบายประการหนึ่งที่น่าสังเกตคือส่วนเสริม DBGridHotPDFExport ซึ่งจะเรนเดอร์ TDBGrid ออกเป็น PDF โดยตรง ผลลัพธ์ส่วนใหญ่จะเป็นกฎแบบสองระดับและข้อความ ดังนั้นเอกสารที่ถูกกำหนดค่าสำหรับ JBIG2 จะทำให้ไฟล์ส่งออกเหล่านี้มีขนาดกะทัดรัดโดยที่คุณไม่ต้องจัดการอะไรเพิ่มเติม มีสองหัวข้อที่เกี่ยวข้องในบล็อกนี้ที่เจาะลึกไปถึงขั้นตอนการทำงานโดยรอบ สำหรับวิธีการจัดวางรูปภาพและแบบอักษรเมื่อคุณสร้างรายงาน โปรดดูที่ ผลลัพธ์ของรายงานพร้อมแบบอักษรและรูปภาพใน Delphi และเมื่อเอกสารที่ถูกบีบอัดต้องสอดคล้องกับโปรไฟล์สำหรับการเก็บถาวร กฎเกณฑ์ใน การตรวจสอบความถูกต้องของ PDF/A, PDF/X และ PDF/UA ใน Delphi จะบอกคุณว่ามีตัวกรองใดบ้างที่ระดับความสอดคล้อง (conformance level) ที่กำหนดนั้นยอมรับ JBIG2 มาพร้อมกับ HotPDF Component สำหรับ Delphi และ C++Builder โดยจะอยู่ถัดจาก API การโหลด การแก้ไข และการเข้ารหัสที่ได้ครอบคลุมไว้ที่อื่นที่นี่