مقاله فنی

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

یک اسلاید پزشکی اسکن شده، یک کاشی نقشه‌برداری هوایی، یک فریم فیلم که در محدوده دینامیکی کامل آرشیو شده است. اینها تصاویری هستند که به عنوان JPEG 2000 به دست می‌رسند، و دلیل خوبی برای این کار وجود دارد. این فرمت 12 یا 16 بیت در هر کانال را نگه می‌دارد، با استفاده از تبدیل موجک (wavelet) به جای تبدیل کسینوسی گسسته (block DCT) که JPEG استفاده می‌کند، فشرده‌سازی را انجام می‌دهد و می‌تواند همان تصویر را به صورت بدون اتلاف (lossless) یا با اتلاف (lossy) از یک جریان کدگذاری کند. هنگامی که سندی ساخته شده از این منابع باید به یک PDF تبدیل شود، تصویر باید از فیلتری عبور کند که مشخصات PDF دقیقاً برای همین کدک رزرو کرده است

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

فیلتر JPXDecode در PDF

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

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

رمزگشا با پیکسل‌ها چه می‌کند

به طور معمول image XObject های PDF انتظار 8 بیت به ازای هر جزء در DeviceGray یا DeviceRGB را دارند. فرمت JPEG 2000 به طور معمول از این مقدار فراتر می‌رود، و مدل اجزای آن عمومی‌تر از یک شطرنجی فشرده است، بنابراین رمزگشا قبل از اینکه داده‌ها به عنوان یک تصویر معمولی قابل استفاده باشند، سه وظیفه برای انجام دادن دارد

اول، اجزای با عمق بیت بالا به 8 بیت تغییر نمونه (resample) داده می‌شوند. یک نمونه 12 بیتی یا 16 بیتی به محدوده 0 تا 255 کوچک می‌شود تا نتیجه یک شبکه شطرنجی 8 بیتی معمولی باشد. اجزای علامت‌دار ابتدا به محدوده بدون علامت منتقل می‌شوند. این جزئیات مهم است زیرا این کار به خودی خود با اتلاف است: یک اسکن خاکستری 16 بیتی در همان لحظه‌ای که به یک تصویر PDF 8 بیتی تبدیل می‌شود، دامنه تُنالیته عمیق خود را از دست می‌دهد، که این معامله مناسبی برای خروجی روی صفحه و چاپ است اما نه برای آرشیو مجدد

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

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

جعبه‌های JP2 در مقابل یک جریان خام J2K

یک فایل JPEG 2000 در دو شکل ارائه می‌شود، و HotPDF به جای پسوند فایل، از طریق اولین بایت‌ها تشخیص می‌دهد که در حال خواندن کدام یک است. یک فایل JP2 یک محفظه با ساختار جعبه‌ای است: با جعبه امضای دوازده بایتی 00 00 00 0C 6A 50 20 20 شروع می‌شود و جریان کد را به همراه جعبه‌هایی که فضای رنگی، وضوح و فراداده (metadata) را توصیف می‌کنند، در بر می‌گیرد. یک جریان کد 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 یک متغیر بولی (boolean) است که در صورت درست بودن (true)، موجک برگشت‌پذیر را انتخاب می‌کند؛ CompressionQuality یک TJpeg2000QualityRange است، عددی صحیح از 1 تا 100 که در آن 1 کوچک و زشت است و 100 بزرگ و وفادار به اصل. مقادیر پیش‌فرض در ثابت‌های نام‌گذاری شده قرار دارند: Jpeg2000DefaultLosslessCompression روی False و Jpeg2000DefaultLossyQuality روی 80 تنظیم شده‌اند

این تصمیم بر اساس محتواست. روش بدون اتلاف (Lossless) برای یک کپی اصلی، یک اسکن پزشکی یا قانونی، یا هر چیزی که ممکن است بعداً مجدداً کدگذاری شود و نباید افت کیفیت نسلی در آن جمع شود، مناسب است. روش با اتلاف در کیفیت 80 برای تصویری که قرار است روی صفحه نمایش داده شود یا چاپ شود مناسب است، جایی که افت تدریجی موجک، فایل به مراتب کوچکتری را بدون ایجاد هیچ نقص بصریِ قابل تشخیصی برای خواننده، فراهم می‌کند. یک هشدار در مورد CMYK وجود دارد که باید به آن اشاره کرد: بیت‌مپ متد SetCMYK را در دسترس قرار می‌دهد تا داده‌های چهار کاناله را به جای RGBA به عنوان CMYK علامت‌گذاری کند، که این موضوع برای خطوط تولید چاپی که تفکیک رنگ‌ها را دست‌نخورده نگه می‌دارند، اهمیت دارد

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 هیچ فیلتر کلی رمزگشایی در هنگام بارگذاری (decode-on-load) برای تصاویر ندارد. وقتی PDFای را باز می‌کنید که از قبل حاوی یک تصویر JPXDecode است، موتور آن جریان را رمزگشایی نمی‌کند. این موتور، بایت‌های JPEG 2000 را دقیقاً همانطور که هستند نگه می‌دارد، بنابراین کپی یک صفحه یا ادغام یک سند، تصویر را دست‌نخورده، بایت به بایت منتقل می‌کند. رمزگشا تنها یک نقطه ورود دارد، و آن در سمت ایجاد است: متد مبتنی بر فایلِ AddImage، که از طریق پسوند فایل برای مدیریت منابع .jp2، .j2k، .jpt و .jpc توزیع می‌شود

این جداسازی یک طراحی درست است تا یک محدودیت. رمزگشایی یک جریان جاسازی شده JPX در هنگام بارگذاری، فقط برای کدگذاری مجدد آن در هنگام ذخیره، یک تصویر بایگانی شده بدون اتلاف را به یک تصویر با اتلاف تبدیل می‌کند و هر ادغامی را متورم می‌کند، و همه اینها برای تصویری است که شما فقط قصد داشتید از یک PDF به PDF دیگر منتقل کنید. عبور دادن بی‌تغییر جریان، یک عملیات بدون اتلاف و سریع است. رمزگشایی به تنها لحظه‌ای موکول می‌شود که واقعاً مورد نیاز است: زمانی که یک فایل JPEG 2000 را از روی دیسک به موتور تحویل می‌دهید و از آن می‌خواهید که آن تصویر را برای قرار دادن در صفحه جدید، شطرنجی (rasterize) کند. در آن نقطه فایل باید به پیکسل تبدیل شود، و رمزگشا اجرا می‌شود

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

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

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