مقال تقني

إضافة صور JPEG 2000 إلى ملفات PDF في Delphi باستخدام HotPDF

شريحة طبية ممسوحة ضوئياً، أو تجانب مسح جوي، أو إطار فيلم مؤرشف بنطاق ديناميكي كامل. هذه هي الصور التي تصل بتنسيق JPEG 2000، وتصل بهذه الطريقة لسبب وجيه. يحتفظ التنسيق بـ 12 أو 16 بتاً لكل قناة، ويضغط باستخدام تحويل مويجي بدلاً من كتل DCT التي يستخدمها JPEG، ويمكنه تشفير نفس الصورة إما بضياع أو بدون ضياع من تدفق تعليمات برمجية واحد. عندما يتعين على مستند مبني من تلك المصادر أن يصبح ملف PDF، يجب أن تنتقل الصورة عبر مرشح تخصصه مواصفات PDF لهذا التشفير بالذات

أعاد الإصدار HotPDF v2.228.0 محرك فك تشفير JPEG 2000 عاملاً لهذا المسار. كانت نسخة أقدم قد شحنت الوحدة مع دوال فارغة تُرجع nil، لذلك كانت واجهة برمجة التطبيقات (API) موجودة ولكن لم تقم بفك تشفير أي شيء. يربط المحرك الحالي OpenJPEG 2.5.4 بشكل ثابت ويحول مصدر JP2 أو J2K إلى بكسلات يمكن لـ HotPDF وضعها في الصفحة

مرشح JPXDecode في PDF

يُعرّف معيار ISO 32000-1 مرشح JPXDecode في القسم 7.4.9. يسمي كائن XObject للصورة في PDF ضغطه في إدخال /Filter في قاموس التدفق، وقيمة JPXDecode هي التي تفيد بأن بيانات التدفق هي تدفق تعليمات JPEG 2000 وليست صورة JPEG أساسية التي يحملها /DCTDecode. المرشح هو ما يسمح لملف PDF باحتواء بيانات صور مضغوطة مويجياً بعمق بت عالي، وهو يقبل كلاً من الوضعين المفقود وغير المفقود للمشفر، لأن الوضع هو خاصية لتدفق التعليمات نفسه وليس للغلاف المحيط به

هذه النقطة الأخيرة هي الجديرة بالاهتمام. إن JPEG 2000 هو خوارزمية واحدة مع حالة خاصة بدون ضياع، وليس تنسيقين منفصلين. يعيد المويجي القابل للانعكاس 5/3 بناء العينات الأصلية تماماً؛ ويبادل المويجي غير القابل للانعكاس 9/7 هذه الدقة بملف أصغر حجماً. يتعامل جهاز فك التشفير مع كليهما بنفس الطريقة وقت القراءة، ولهذا السبب يحتاج HotPDF إلى مسار فك تشفير واحد فقط لقبول أي شيء يلقيه تدفق JPXDecode عليه

ما يفعله جهاز فك التشفير بالبكسل

تتوقع كائنات XObject للصور في PDF في الحالة الشائعة 8 بت لكل مكون في DeviceGray أو DeviceRGB. غالباً ما يتجاوز JPEG 2000 ذلك، ونموذج المكونات الخاص به أعم من كونه شبكة نقطية معبأة، لذلك لدى جهاز فك التشفير ثلاث وظائف للقيام بها قبل أن تكون البيانات قابلة للاستخدام كصورة عادية

أولاً، يتم إعادة أخذ عينات المكونات ذات عمق البت العالي إلى 8 بتات. يتم تقليل حجم العينة ذات الـ 12 أو الـ 16 بتاً إلى النطاق من 0 إلى 255 بحيث تكون النتيجة شبكة نقطية عادية من 8 بتات. يتم تحويل المكونات الموقعة إلى نطاق غير موقع أولاً. التفاصيل مهمة لأنها في حد ذاتها تفقدية: يفقد المسح الضوئي ذو التدرج الرمادي 16 بتاً نطاقه اللوني العميق في اللحظة التي يصبح فيها صورة PDF ذات 8 بتات، وهي المقايضة الصحيحة للعرض على الشاشة والطباعة ولكن ليس لإعادة الأرشفة

ثانياً، يتم تحويل مساحة ألوان YCbCr (يسميها المشفر SYCC) إلى RGB. غالباً ما يقوم JPEG 2000 بتخزين الألوان في مساحة luma-chroma لكفاءة الضغط، وهي نفس الفكرة التي يستخدمها JPEG الأساسي، ويقوم جهاز فك التشفير بتطبيق التحويل العكسي القياسي بحيث تتلقى الصفحة ألوان RGB الحقيقية

ثالثاً، يتم رفع عينات المكونات الفرعية عن طريق تكرار أقرب جار. يتم تخزين قنوات chroma بشكل متكرر بنصف الدقة، لذلك يقرأ جهاز فك التشفير كل مكون بأبعاده وعامل أخذ العينات الخاص به، ثم يكرر العينات لرفع كل قناة إلى حجم الصورة الكامل قبل التداخل. التكرار لأقرب جار يبقي هذه الخطوة غير مكلفة؛ كانت chroma التي تملأها منخفضة التردد في البداية، لذا فالتكلفة المرئية ضئيلة

صناديق JP2 مقابل تدفق تعليمات J2K الخام

يأتي ملف JPEG 2000 في شكلين، ويكتشف HotPDF أيهما يقرأ من البايتات الأولى بدلاً من امتداد الملف. ملف JP2 عبارة عن حاوية مهيكلة في صناديق: يبدأ بصندوق التوقيع المكون من اثني عشر بايتاً 00 00 00 0C 6A 50 20 20 ويغلف تدفق التعليمات جنباً إلى جنب مع الصناديق التي تصف مساحة الألوان والدقة والبيانات الوصفية. لا يحمل تدفق تعليمات J2K الخام أي حاوية على الإطلاق ويبدأ بعلامة SOC FF 4F FF 51. يقرأ جهاز فك التشفير تلك البايتات البادئة، ويتعرف على التوقيع، ويحدد برنامج ترميز OpenJPEG المطابق لكل حالة

يتم التعامل مع كلا الشكلين لأن كليهما يحدث في الممارسة العملية. تقوم أجهزة الالتقاط والأرشيفات التي تحتاج إلى البيانات الوصفية الجانبية بإنتاج JP2؛ وتنتج الأدوات التي تريد أصغر حمولة ممكنة تدفق التعليمات المجرد. تمت صياغة نوع التنسيق كتعداد، TJpeg2000FileType، مع الأعضاء jtInvalid و jtJP2 و jtJ2K و jtJPT. العضو JPT يسمى البديل لتدفق JPIP؛ يحلل كاشف توقيع البايت الشكلين اللذين يمكنه فك تشفيرهما، JP2 و J2K، ويبلغ عن أي شيء آخر على أنه jtInvalid بحيث يفشل الإدخال غير المدعوم بشكل نظيف بدلاً من إنتاج بيانات تالفة

uses
  HPDFJpeg2000;

var
  Decoder: THPDFJpeg2000Decoder;
  Pixels: TJpeg2000ByteArray;
begin
  Decoder := THPDFJpeg2000Decoder.Create;
  try
    if Decoder.LoadFromStream(Input) then          // JP2 or J2K, auto-detected
      if Decoder.GetImageData(Pixels) then
        // Pixels is 8-bit interleaved, ColorComponents channels wide,
        // row-major top to bottom: ready for a DeviceGray/DeviceRGB XObject.
        ProcessRaster(Decoder.Width, Decoder.Height,
                      Decoder.ColorComponents, Pixels);
  finally
    Decoder.Free;
  end;
end;

بدون ضياع ومفقود على جانب التشفير

يقرأ جهاز فك التشفير كلا الوضعين دون إخباره أيهما. يصبح الاختيار معلمة فقط عندما تذهب في الاتجاه الآخر وتنتج ملف JPEG 2000، وهو ما يمكن أن يفعله HotPDF أيضاً من خلال الفئة TJpeg2000Bitmap، وهي امتداد لفئة TBitmap التي تقوم بتحميل وحفظ البيانات النقطية بصيغة JP2. تحكم خاصيتان في الإخراج. LosslessCompression هي قيمة منطقية تختار المويجي القابل للانعكاس عندما تكون صحيحة؛ CompressionQuality عبارة عن TJpeg2000QualityRange، وهو عدد صحيح من 1 إلى 100 حيث 1 صغير وقبيح و 100 كبير وصادق للصورة الأصلية. تعيش الإعدادات الافتراضية في ثوابت مسماة: Jpeg2000DefaultLosslessCompression هي False و Jpeg2000DefaultLossyQuality هي 80

إن القرار هنا هو قرار يعتمد على المحتوى. يناسب خيار بدون ضياع نسخة رئيسية، أو مسحاً ضوئياً طبياً أو قانونياً، أو أي شيء قد يتم إعادة تشفيره لاحقاً ويجب ألا تتراكم فيه الخسارة الجيلية. أما الخيار المفقود بجودة 80 فيناسب صورة متجهة إلى الشاشة أو الطباعة، حيث يوفر التدهور الرشيق للمويجي ملفاً أصغر حجماً بشكل ملحوظ دون أي تشوه قد يلاحظه القارئ. هناك تحذير واحد لـ CMYK يجب الإشارة إليه: تكشف الصورة النقطية عن SetCMYK لوضع علامة على البيانات المكونة من أربع قنوات على أنها CMYK بدلاً من RGBA، وهو أمر مهم لخطوط أنابيب الطباعة التي تبقي على فصل الألوان سليماً

uses
  HPDFJpeg2000;

var
  Bmp: TJpeg2000Bitmap;
begin
  Bmp := TJpeg2000Bitmap.Create;
  try
    Bmp.LoadFromStream(Source);              // decode an existing JP2/J2K
    Bmp.LosslessCompression := True;         // reversible 5/3 wavelet
    // or, for a smaller lossy file:
    // Bmp.LosslessCompression := False;
    // Bmp.CompressionQuality := 80;         // matches the default
    Bmp.SaveToStream(Output);                // always writes a JP2 file
  finally
    Bmp.Free;
  end;
end;

لماذا لا يوجد خط أنابيب لمرشح فك التشفير عند التحميل

هناك حقيقة معمارية تشكل كيفية استخدامك لأي من هذا، ومن السهل افتراض العكس. لا يمتلك HotPDF مرشحاً عاماً لفك تشفير الصور عند التحميل. عند فتح ملف PDF يحتوي بالفعل على صورة JPXDecode، لا يقوم المحرك بفك تشفير هذا التدفق. بل يحتفظ ببايتات JPEG 2000 كما هي بالضبط، بحيث ينقل نسخ الصفحة أو دمج المستند الصورة كما هي دون تغيير، بايتاً ببايت. يحتوي جهاز فك التشفير على نقطة إدخال واحدة، وهي في جانب الإنشاء: الإجراء القائم على الملف AddImage، والذي يوجه بناءً على امتداد الملف للتعامل مع المصادر .jp2 و .j2k و .jpt و .jpc

هذا التقسيم هو التصميم الصحيح بدلاً من أن يكون قيداً. إن فك تشفير تدفق JPX مضمن عند التحميل، فقط لإعادة تشفيره عند الحفظ، من شأنه أن يحول صورة مؤرشفة بدون ضياع إلى صورة مفقودة وتضخم كل عملية دمج، وكل ذلك من أجل صورة أردت فقط نقلها من ملف PDF إلى آخر. تمرير التدفق حرفياً هو عملية غير مفقودة وسريعة. يتم تأجيل فك التشفير إلى اللحظة الوحيدة التي يكون فيها مطلوباً بالفعل: عندما تسلم المحرك ملف JPEG 2000 من القرص وتطلب منه تنقيط تلك الصورة لوضعها في صفحة جديدة. في تلك النقطة، يجب أن يصبح الملف بكسلات، ويعمل جهاز فك التشفير

تسجيل الدعم ووضع الصورة

إن تسجيل صور JPEG 2000 هو خيار اختياري خلف مفتاح الترجمة HPDF_REGISTER_JPEG2000_PICTURE، والذي يتم إيقاف تشغيله افتراضياً. السبب هو تعارض حقيقي، وليس الحذر: تسجيل تنسيقات الملفات jp2 و j2k و jpc عالمياً مع TPicture يمكن أن يتداخل مع اكتشاف تنسيق BLOB الذي يعتمد عليه TppDBImage الخاص بـ ReportBuilder. حدد المفتاح عندما لا يكون هذا التكامل قيد التشغيل، وتسجل تنسيقات الملفات بحيث يتعرف عليها TPicture؛ اتركه غير محدد ويظل توجيه الامتداد AddImage يفك تشفير ملفات JPEG 2000 مباشرة، لأن هذا المسار لا يمر عبر TPicture على الإطلاق

بعد فهم ذلك، فإن وضع صورة JPEG 2000 هو نفس الإيقاع المكون من ثلاثة استدعاءات مثل أي صورة أخرى من صور HotPDF. مرر إلى AddImage مسار .jp2 ونوع ضغط لكيفية تخزين الصورة في المخرجات، ثم ضع فهرس الصورة المُرجع على الصفحة باستخدام ShowImage

var
  Pdf: THotPDF;
  ImgIndex: Integer;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.BeginDoc;
    Pdf.AddPage;
    // The .jp2 source is decoded through the OpenJPEG backend, then
    // re-embedded with the compression you request here.
    ImgIndex := Pdf.AddImage('Scan_16bit.jp2', icJpeg);
    // x, y, width, height in points; final 0 is the rotation angle.
    Pdf.ShowImage(ImgIndex, 72, 72, 400, 300, 0);
    Pdf.EndDoc;
  finally
    Pdf.Free;
  end;
end;

يتحكم الضغط الذي تمرره إلى AddImage في كيفية إعادة تخزين الصورة التي تم فك تشفيرها، وليس في كيفية قراءتها. يمكن أن تخرج ملفات JPEG 2000 التي تم فك تشفيرها إلى صورة نقطية مرة أخرى كصورة DCTDecode JPEG أو شبكة نقطية Flate أو مرشح مدعوم آخر، أيهما يناسب المستند. يحدث فك التشفير من JP2 أو J2K أولاً بغض النظر، لذا يقبل نفس الاستدعاء مصدراً مضغوطاً مويجياً ويضمنه بأي شكل يتوقعه بقية خط الأنابيب الخاص بك

للحصول على صورة أوسع لكيفية استقرار الصور والخطوط في المخرجات المنشأة، راجع ملاحظاتنا حول إخراج التقارير مع الخطوط والصور. عندما يعيد المستند الذي تقوم بتجميعه استخدام المحتوى من ملفات PDF الموجودة، فإن سلوك التمرير الموصوف هنا يقترن بآليات الدمج والمراجعة في تدفقات الكائنات والتحديثات الإضافية. يتم شحن محرك فك تشفير JPEG 2000 كجزء من HotPDF Component لـ Delphi و C++Builder، إلى جانب واجهات برمجة تطبيقات الصور والخطوط والمستندات التي تمت تغطيتها في مكان آخر في هذه المدونة