Technical Article

افزودن تصاویر JPEG 2000 به PDF در دلفی با HotPDF

یک اسلاید پزشکی اسکن‌شده، یک کاشی نقشه‌برداری هوایی، یک فریم فیلم که با دامنه دینامیکی کامل بایگانی شده است. این‌ها تصاویری هستند که با فرمت JPEG 2000 ارائه می‌شوند و به دلیلی با این فرمت می‌رسند. این فرمت ۱۲ یا ۱۶ بیت در هر کانال را حفظ می‌کند، به جای DCT بلوکی که در JPEG استفاده می‌شود با تبدیل موجک (wavelet) فشرده‌سازی می‌کند، و می‌تواند همان تصویر را به صورت بدون اتلاف (lossless) یا بااتلاف (lossy) از یک استریم کد انکود کند. هنگامی که سندی که از این منابع ساخته شده است باید به یک PDF تبدیل شود، تصویر باید از فیلتری عبور کند که مشخصات PDF دقیقاً برای همین کدک در نظر گرفته است

HotPDF نسخه 2.228.0 یک موتور دیکود فعال JPEG 2000 را برای آن مسیر بازیابی کرد. بیلد قبلی، یونیت را با توابع خالی ارائه می‌داد که nil برمی‌گرداندند، بنابراین API وجود داشت اما چیزی را دیکود نمی‌کرد. موتور فعلی OpenJPEG 2.5.4 را به صورت ایستا پیوند می‌دهد و یک منبع JP2 یا J2K را به پیکسل‌هایی تبدیل می‌کند که HotPDF می‌تواند در صفحه قرار دهد

فیلتر JPXDecode در PDF

استاندارد ISO 32000-1 فیلتر JPXDecode را در بخش ۷.۴.۹ تعریف می‌کند. یک image XObject در PDF فشرده‌سازی خود را در ورودی /Filter دیکشنری استریم نام‌گذاری می‌کند، و JPXDecode مقداری است که می‌گوید داده‌های استریم یک استریم کد JPEG 2000 هستند و نه JPEG پایه که /DCTDecode حمل می‌کند. این فیلتر همان چیزی است که اجازه می‌دهد یک PDF داده‌های تصویر فشرده‌شده با موجک و عمق بیت بالا را نگه دارد، و هر دو حالت بدون اتلاف و بااتلاف کدک را می‌پذیرد، زیرا حالت، ویژگی خود استریم کد است و نه بسته‌بندی اطراف آن

نکته آخر همان چیزی است که ارزش به خاطر سپردن دارد. JPEG 2000 یک الگوریتم واحد با یک حالت خاص بدون اتلاف است، نه دو فرمت مجزا. موجک بازگشت‌پذیر ۵/۳ نمونه‌های اصلی را دقیقاً بازسازی می‌کند؛ موجک غیرقابل بازگشت ۹/۷ آن دقت را در ازای یک فایل کوچک‌تر مبادله می‌نماید. یک دیکودر با هر دو در زمان خواندن به یک شکل برخورد می‌کند، به همین دلیل است که HotPDF تنها به یک مسیر دیکود نیاز دارد تا هر آنچه را که یک استریم JPXDecode به آن می‌دهد، بپذیرد

آنچه دیکودر با پیکسل‌ها انجام می‌دهد

image XObjectهای PDF در حالت معمول انتظار ۸ بیت در هر جزء رنگی (component) را در DeviceGray یا DeviceRGB دارند. فرمت JPEG 2000 معمولاً از آن فراتر می‌رود، و مدل جزء آن عمومی‌تر از یک شطرنجی بسته‌بندی شده (packed raster) است، بنابراین دیکودر باید قبل از اینکه داده‌ها به عنوان یک تصویر عادی قابل استفاده باشند، سه کار انجام دهد

اول، اجزای رنگی با عمق بیت بالا به ۸ بیت نمونه‌گیری مجدد می‌شوند. یک نمونه ۱۲ بیتی یا ۱۶ بیتی به محدوده ۰ تا ۲۵۵ کاهش می‌یابد تا نتیجه یک شطرنجی ۸ بیتی معمولی باشد. اجزای رنگی علامت‌دار (Signed) ابتدا به محدوده بدون علامت (unsigned) منتقل می‌شوند. این جزئیات مهم هستند زیرا خودشان باعث افت کیفیت (lossy) می‌شوند: یک اسکن سیاه‌وسفید ۱۶ بیتی به محض تبدیل شدن به تصویر PDF 8 بیتی، دامنه تونال عمیق خود را از دست می‌دهد، که این معامله برای خروجی صفحه‌نمایش و چاپ مناسب است اما برای آرشیو مجدد نه

دوم، یک فضای رنگی YCbCr (کدک آن را SYCC می‌نامد) به RGB تبدیل می‌شود. فرمت JPEG 2000 اغلب رنگ را در یک فضای درخشندگی-رنگی (luma-chroma) برای کارایی فشرده‌سازی ذخیره می‌کند، همان ایده‌ای که JPEG پایه استفاده می‌نماید، و دیکودر تبدیل معکوس استاندارد را اعمال می‌کند تا صفحه RGB واقعی را دریافت کند

سوم، اجزایی که تحت نمونه‌برداری کاهشی (subsampled) قرار گرفته‌اند، از طریق تکرار نزدیک‌ترین همسایه (nearest-neighbor) افزایش مقیاس (upsampled) می‌یابند. کانال‌های رنگی اغلب با وضوح نصف ذخیره می‌شوند، بنابراین دیکودر هر جزء را در ابعاد و با ضریب نمونه‌برداری خود می‌خواند، سپس نمونه‌ها را تکرار می‌کند تا هر کانال را قبل از در هم آمیختن (interleaving) به اندازه تصویر کامل برساند. تکرار نزدیک‌ترین همسایه این مرحله را کم‌هزینه نگه می‌دارد؛ کانال رنگی که پر می‌شود از ابتدا فرکانس پایینی داشت، بنابراین هزینه بصری آن اندک است

باکس‌های 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 یک مقدار بولی است که در صورت درست بودن (true)، موجک بازگشت‌پذیر را انتخاب می‌کند؛ CompressionQuality یک TJpeg2000QualityRange است، عدد صحیحی از ۱ تا ۱۰۰ که ۱ به معنای کوچک و بی‌کیفیت و ۱۰۰ به معنای بزرگ و وفادار به اصل است. مقادیر پیش‌فرض در ثابت‌های نام‌گذاری شده قرار دارند: Jpeg2000DefaultLosslessCompression برابر False است و Jpeg2000DefaultLossyQuality روی 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 به PDF دیگری منتقل کنید. عبور دادن بی‌کم‌وکاست استریم یک عملیات بدون اتلاف و سریع است. دیکود کردن به تنها لحظه‌ای موکول می‌شود که واقعاً به آن نیاز است: زمانی که یک فایل JPEG 2000 را از روی دیسک به موتور تحویل می‌دهید و از آن می‌خواهید آن تصویر را برای قرارگیری در یک صفحه جدید شطرنجی‌سازی (rasterize) کند. در این مرحله، فایل باید به پیکسل تبدیل شود، و دیکودر اجرا می‌گردد

ثبت پشتیبانی و قرار دادن یک تصویر

ثبت تصویر JPEG 2000 به صورت اختیاری در پشت سوییچ کامپایل HPDF_REGISTER_JPEG2000_PICTURE قرار دارد، که به طور پیش‌فرض خاموش است. دلیل آن یک تداخل واقعی است، نه احتیاط: ثبت جهانی فرمت‌های فایل jp2، j2k و jpc در TPicture می‌تواند با تشخیص فرمت BLOB که TppDBImage در ReportBuilder به آن متکی است، تداخل ایجاد نماید. این سوییچ را زمانی تعریف کنید که آن یکپارچه‌سازی در میان نباشد، و فرمت‌های فایل ثبت می‌شوند تا TPicture آن‌ها را بشناسد؛ اگر آن را تعریف‌نشده باقی بگذارید، باز هم فرآیند ارسال پسوند AddImage فایل‌های JPEG 2000 را به طور مستقیم دیکود می‌کند، زیرا آن مسیر اصلاً از طریق TPicture عبور نمی‌نماید

با درک این موضوع، قرار دادن یک تصویر JPEG 2000 همان ریتم سه‌فراخوانی است که برای هر تصویر دیگر HotPDF استفاده می‌شود. یک مسیر .jp2 و یک نوع فشرده‌سازی برای نحوه ذخیره تصویر در خروجی را به AddImage بدهید، سپس شاخص تصویر بازگردانده‌شده را با استفاده از 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 raster یا یک فیلتر پشتیبانی‌شده دیگر (هر کدام که برای سند مناسب‌تر است) خروجی داده شود. دیکود از JP2 یا J2K در هر صورت ابتدا رخ می‌دهد، بنابراین همان فراخوانی یک منبع فشرده‌شده با موجک را می‌پذیرد و آن را در هر شکلی که بقیه خط لوله شما انتظار دارد، جاسازی می‌کند

برای مشاهده تصویر کلی‌تر از نحوه قرارگیری تصاویر و فونت‌ها در خروجی تولیدشده، یادداشت‌های ما در مورد خروجی گزارش با فونت‌ها و تصاویر در دلفی را ببینید. زمانی که سندی که مونتاژ می‌کنید از محتوای PDFهای موجود مجدداً استفاده می‌کند، رفتار عبور بی‌کم‌وکاست (passthrough) که در اینجا توضیح داده شد، با مکانیک‌های ادغام و تجدید نظر در استریم‌های شی و به‌روزرسانی‌های افزایشی همخوانی دارد. موتور دیکود JPEG 2000 به عنوان بخشی از کامپوننت HotPDF برای دلفی و C++Builder، در کنار APIهای تصویر، فونت و سند که در جاهای دیگر این وبلاگ پوشش داده شده‌اند، ارائه می‌شود