مقاله فنی

رندر کردن رنگ‌های Separation و DeviceN Spot در دلفی

کامپوننت HotPDF رنگ‌های Separation و DeviceN spot را در صفحات بارگذاری‌شده PDF از طریق حل فضای رنگ با استفاده از HPDFResolveColorSpace، ارزیابی تابع تبدیل تینت (tint-transform) با HPDFEvalTintTransform، و تبدیل نتیجه از طریق فضای جایگزین به RGB برای نمایشگر رندر می‌کند. از نسخه v2.375.0، این خط لوله هر چهار نوع تابع PDF را ارزیابی می‌کند، از جمله ماشین‌حساب‌های Type 4 PostScript، بنابراین یک فایل آماده چاپ با جوهر‌های Pantone رنگ‌های واقعی خود را به جای یک نگهدارنده نشان می‌دهد. این مقاله نحوه عملکرد این خط لوله و به همان اندازه نحوه شکست آن را در حین ساخت نشان می‌دهد

مورد شروع همیشه یکسان است. یک مشتری یک فایل PDF را از چاپخانه دریافت می‌کند: عنوان با یک جوهر spot نام‌گذاری‌شده تنظیم شده است، طرح بسته‌بندی از ترکیب DeviceN دو جوهر استفاده می‌کند و متن بدنه مشکی ساده است. در Acrobat عالی به نظر می‌رسد. در برنامه دلفی شما، عنوان مشکی رندر می‌شود یا بدتر از آن، اصلاً رندر نمی‌شود و مشتری به جای فایل، یک گزارش باگ علیه نرم‌افزار شما ثبت می‌کند. فایل خوب است. رندرکننده فقط با فضاهای رنگی که فایل استفاده می‌کند صحبت نمی‌کند

چرا رنگ‌های spot در نمایشگر PDF مشکی رندر می‌شوند؟

رنگ‌های spot زمانی مشکی رندر می‌شوند یا از بین می‌روند که رندرکننده فقط اپراتورهای رنگ دستگاه (rg، g، k) را پیاده‌سازی کند و از اپراتورهای عمومی چشم‌پوشی کند. استاندارد ISO 32000-1 §8.6 سه گروه از فضاهای رنگی را تعریف می‌کند: فضاهای دستگاه (DeviceGray، DeviceRGB، DeviceCMYK)، فضاهای مبتنی بر CIE (مانند CalGray، CalRGB، Lab، ICCBased)، و فضاهای ویژه (مانند Indexed، Separation، DeviceN، Pattern). هر چیزی خارج از گروه دستگاه با اپراتورهای عمومی انتخاب می‌شود: عملگرهای cs و CS یک فضا را با نام از دیکشنری منابع صفحه انتخاب می‌کنند، سپس sc، SC، scn و SCN مقادیر مؤلفه را تامین می‌کنند. رندرکننده‌ای که از این عملگرها عبور می‌کند، هر رنگی را که آخرین بار تنظیم شده بود نگه می‌دارد، که برای صفحه‌ای که با عنوان رنگ spot خود شروع می‌شود، همان مشکی اولیه DeviceGray است

کامپوننت HotPDF مجموعه اپراتورهای عمومی را در نسخه v2.333.0 به رندرکننده صفحه خود اضافه کرد، به همراه یک مسیر وضوح یکپارچه: هر ورودی منبع /ColorSpace، چه یک نام خالی، یک آرایه درون‌خطی یا یک ارجاع غیرمستقیم باشد، به یک رکورد THPDFColorSpace تجزیه می‌شود و هر درخواست رنگ پر کردن (fill) یا استروک (stroke) از طریق یک فراخوانی واحد HPDFResolveColor هدایت می‌شود. شمارش خانواده پوشش‌دهی را در یک نگاه نشان می‌دهد

type
  THPDFColorSpaceFamily = (csfDeviceGray, csfDeviceRGB, csfDeviceCMYK,
                           csfIndexed, csfCalGray, csfCalRGB, csfLab,
                           csfICCBased, csfSeparation, csfDeviceN,
                           csfUnsupported);

function HPDFResolveColorSpace(Obj: THPDFObject): THPDFColorSpace;

function HPDFResolveColor(const CS: THPDFColorSpace;
  const Comps: THPDFColorComps; CompCount: Integer): THPDFRenderColor;

یک تصمیم طراحی ارزش خود را بارها نشان می‌دهد: csfUnsupported یک خانواده درجه یک است و نه یک خطا. فضایی که رندرکننده نتواند آن را تفسیر کند، به یک جایگزین مشخص تنزل می‌یابد، به جای اینکه رندر صفحه را متوقف کند؛ این رفتار با نحوه کار نمایشگرهای استاندارد مطابقت دارد و مانع از آن می‌شود که یک پر کردن غیرمعمول، کل سند را خالی نشان دهد

چگونه تبدیل تینت یک مقدار جوهر را به یک رنگ واقعی تبدیل می‌کند؟

یک فضای Separation سه بخش از اطلاعات را حمل می‌کند: نام جوهر، یک فضای رنگی جایگزین و یک تابع تبدیل تینت (tint-transform). آرایه [/Separation /PANTONE485 /DeviceCMYK f] می‌گوید: وقتی استریم محتوا عبارت 0.8 scn را می‌نویسد، مقدار تینت ۰.۸ را به تابع f بدهید و چهارگانه CMYK حاصل را رسم کنید. DeviceN این مورد را به N جوهر با یک تابع ورودی N تعمیم می‌دهد. نام خود جوهر در صفحه فقط جنبه راهنمایی دارد؛ تبدیل تینت کل معنای رندرینگ است، بنابراین رندرکننده‌ای که فضا را تجزیه می‌کند اما از تابع عبور می‌کند، هنوز کار مفیدی انجام نداده است

متد HPDFEvalTintTransform موتور تابع در پشت آن مرحله است. این متد که در نسخه v2.334.0 ارائه شد، توابع نمایی Type 2 (فرمول C0 + x^N * (C1 - C0) با کلمپینگ دامنه و محدوده)، توابع بخیه زدن Type 3 (تکرار تابع فرعی انتخاب شده با Bounds به همراه نگاشت مجدد Encode)، و توابع نمونه‌برداری‌شده Type 0 را با نمونه‌های ۸، ۱۶ و ۳۲ بیتی ارزیابی می‌کند. Type 0 همان ساختار جدول جستجو (look-up table) است که ما از سمت تولید در مقاله ساخت جدول‌های رنگی Type 0 پوشش دادیم؛ سمت رندر همان ساختار یکسان را به صورت معکوس طی می‌کند، از بایت‌های نمونه رمزگشایی شده به مقادیر مؤلفه

توابع ماشین‌حساب Type 4 PostScript آخرین مقاومت بودند. تا نسخه v2.375.0، آنها به آرامی به یک نگهدارنده خنثی تبدیل می‌شدند؛ از نسخه v2.375.0 متد HPDFEvalPostScriptCalculator مجموعه عملگرهای کامل استاندارد ISO 32000-1 §7.10.5 را روی یک پشته عملوند محدود اجرا می‌کند: محاسبات، مقایسه، عملگرهای بولین و بیتی، دستکاری پشته از جمله roll، و شرط‌های if/ifelse. منطق جزئیات سخت‌گیرانه‌تر از آن چیزی است که به نظر می‌رسد. متد round در PostScript مقادیر اعشاری نیم را به سمت مقدار بزرگتر گرد می‌کند، بنابراین متد بانکی Round دلفی نمی‌تواند استفاده شود؛ عملگرهای مثلثاتی با درجه کار می‌کنند، به طوری که atan مقادیر را در بازه [۰، ۳۶۰) برمی‌گرداند؛ و exp یک توان دو عملوندی است و نه تابع نمایی طبیعی. همین ارزیابی‌کننده، پر کردن گرادیان مبتنی بر تابع را نیز هدایت می‌کند، به همین دلیل است که رندر سایه‌های محوری و شعاعی شیب‌های هدایت‌شده با ماشین‌حساب را در همان نسخه دریافت کرد

// Evaluate a Separation/DeviceN tint transform (Type 0/2/3/4).
function HPDFEvalTintTransform(FuncObj: THPDFObject;
  const Inputs: THPDFColorComps; InputCount: Integer;
  out AltComps: THPDFColorComps): Boolean;

// Type 4 PostScript calculator, ISO 32000-1 7.10.5 operator set.
function HPDFEvalPostScriptCalculator(const Prog: TBytes;
  const Inputs: THPDFColorComps; InputCount: Integer;
  var Outputs: THPDFColorComps; OutCount: Integer): Boolean;

صداقت در مورد دقت: یک ماشین‌حساب نرم‌افزاری که با دقت مضاعف ارزیابی می‌شود، بیت به بیت با یک RIP مطابقت نخواهد داشت و کلمپینگ در مرز محدوده می‌تواند با یک بایت با ارزش کمتر با پیاده‌سازی دیگر تفاوت داشته باشد. برای نمایش روی صفحه و رندر رگرسیون این موضوع اهمیتی ندارد؛ اگر در حال ساخت پروفر مدیریت رنگ هستید، تبدیل تینت تنها مرحله اول است و شما همچنان به یک CMM واقعی در پایین‌دست نیاز دارید

CalGray، CalRGB، Lab و ICCBased بدون موتور ICC

خانواده‌های مبتنی بر CIE شاخه دیگر همان حل‌کننده را می‌گیرند. HotPDF مقادیر Lab را از طریق زنجیره استاندارد Lab به XYZ به sRGB، شامل مکعب نقطه شکست ۶/۲۹ در تابع انتقال معکوس تبدیل می‌کند و با CalRGB با گامای هر کانال به علاوه ماتریس خطی ۳x۳ و با CalGray با گامای تک آن برخورد می‌کند. جزئیات مربوط به عملکرد، مدیریت نقطه سفید است: ماتریس تبدیل XYZ به sRGB با نقطه سفید اعلام شده در فضای رنگی سازگار است و در رکورد حل‌شده THPDFColorSpace کش می‌شود، بنابراین کار در سطح هر پیکسل بدون توجه به اینکه منبع نور اعلام شده چقدر غیرمعمول باشد، یک ضرب ۳x۳ ساده باقی می‌ماند

با فضاهای ICCBased برخورد کاملاً کاربردی انجام می‌شود. مشخصات PDF از هر استریم ICCBased می‌خواهد که یک فضای /Alternate یا یک فضای ضمنی را از طریق تعداد مؤلفه /N خود اعلام کند، دقیقاً به این دلیل که بیننده‌های فاقد موتور مدیریت رنگ همچنان بتوانند به درستی رندر کنند. HotPDF فضای ICCBased را از طریق آن فضای جایگزین حل می‌کند، یا از روی مقدار /N که ۱، ۳ یا ۴ است، در صورت غیاب ورودی، حدس می‌زند که DeviceGray، DeviceRGB یا DeviceCMYK است و هرگز بایت‌های پروفایل را تجزیه نمی‌کند. این به معنای عدم وابستگی به lcms و عدم پرداخت هزینه جستجوی پروفایل است، به قیمت از دست رفتن دقت رنگ‌سنجی: یک فضای ICCBased که پروفایل آن به شدت از فضای جایگزینش منحرف می‌شود، رندر جایگزین را نمایش خواهد داد. برای مشاهده روی صفحه و تصاویر بندانگشتی، این همان معامله‌ای است که هر بیننده سبکی انجام می‌دهد و این مرزی است که باید در مستندات خود به وضوح بیان کنید

باگی که باعث شد هر جستجوی فضای رنگی نام‌گذاری‌شده با شکست مواجه شود

ساختار اپراتورهای نسخه v2.333.0 با نقصی ارسال شد که برای چهل و دو نسخه نامرئی ماند: هندلرهای cs و CS عملوند خود را با اسلش پیشرو دست‌نخورده (/CS0)، در برابر کلیدهای دیکشنری منابع ذخیره شده بدون اسلش (CS0) جستجو می‌کردند. هر جستجوی فضای رنگی نام‌گذاری‌شده در ۱۰۰ درصد مواقع شکست می‌خورد و کد بی سروصدا به DeviceGray پیش‌فرض بازمی‌گشت. علامت قابل مشاهده به بدترین شکل ظریف بود: پر کردن Separation با مقدار 1 scn به DeviceGray 1.0 تبدیل می‌شد که رنگ سفید را ترسیم می‌کند، و جوهر سفید روی صفحه سفید خطای رندری نیست که کسی از آن اسکرین‌شات بگیرد. راه حل در نسخه v2.375.0، یک هلپر نرمال‌سازی نام مشترک بود که در هر جستجوی عملوند به منبع اعمال شد

نقص‌های مشابه و اصلاحات

دو نقص مرتبط دیگر از همان بررسی کشف شد. اول اینکه ارجاعات غیرمستقیم به اشیای با مقدار آرایه، بدون وضوح برگردانده می‌شدند: دسترسی به سند در رندرکننده حل‌کننده‌های تایپ‌شده را فقط برای استریم‌ها و دیکشنری‌ها داشت، بنابراین /CS0 5 0 R که به یک آرایه مستقل [/Separation ...] اشاره داشت، به عنوان مرجع خام بازمی‌گشت و فضا به عنوان پشتیبانی‌نشده تفسیر می‌شد. دوم اینکه HPDFReadNumericArray معناشناسی طول سخت‌گیرانه‌ای را اعمال می‌کرد که مستلزم آن بود که آرایه PDF حداقل به اندازه بافر ارائه شده باشد. بنابراین خواندن مقادیر /C0 و /C1 یک تابع Type 2 در یک بافر چهار عنصری برای جایگزین‌های تک و سه مؤلفه‌ای با شکست مواجه می‌شد و هر دو آرایه را صفر باقی می‌گذاشت و هر تینت نمایی غیر CMYK مشکی رندر می‌شد تا اینکه نسخه v2.376.0 خواننده منعطف HPDFReadNumericArrayUpTo را معرفی کرد. درس قابل انتقال: هر کلید PDF که مستند شده است حامل یک آرایه عددی با طول متغیر است، باید با خواننده‌ای خوانده شود که آنچه را که وجود دارد پر می‌کند، زیرا یک بافر با اندازه ثابت با تطابق دقیق، فایل‌های معتبر را به صفرهای خاموش تبدیل می‌کند

چگونه رندر رنگ spot را بدون فریب دادن خود تست کنیم؟

مجموعه تست در تمام این مدت سبز باقی ماند. رگرسیون اصلی، یعنی SeparationRendersDistinguishable، فقط ادعا می‌کرد که بیت‌مپ رندر شده کاملاً مشکی نیست. خط لوله‌ای که هر رنگ spot را به DeviceGray کاهش می‌داد، خروجی خاکستری و سفید تولید می‌کرد که مشکی نیست، بنابراین ادعا در حالی که کل ویژگی کار نمی‌کرد، پاس می‌شد. ادعاهای ضعیف به شکل "خالی نیست"، "کاملاً مشکی نیست" یا "مقدار غیرصفر است" نمی‌توانند رندرکننده کارآمد را از رندرکننده خراب تشخیص دهند، زیرا تقریباً هر حالت خرابی همچنان تعدادی پیکسل تولید می‌کند

سبک ادعایی که در واقع این خرابی‌ها را کشف می‌کند، رنگ مورد انتظار را قفل می‌کند: یک PDF مینیمال دست‌ساز را رندر کنید که جوهر Separation آن به یک رنگ شناخته‌شده منتهی می‌شود، سپس پیکسل‌هایی را که در آن رنگ غالب هستند بشمارید. رندر به یک بیت‌مپ برای بررسی از همان نقطه ورود RenderLoadedPageToBitmap استفاده می‌کند که در راهنمای رندر صفحه به بیت‌مپ توضیح داده شده است

var
  Pdf: THotPDF;
  Bmp: TBitmap;
  X, Y, RedHits: Integer;
  Px: TColor;
begin
  Pdf := THotPDF.Create(nil);
  try
    if Pdf.LoadFromFile('spot-red-fixture.pdf', '') > 0 then
    begin
      Bmp := Pdf.RenderLoadedPageToBitmap(0, 96);
      try
        RedHits := 0;
        for Y := 0 to Bmp.Height - 1 do
          for X := 0 to Bmp.Width - 1 do
          begin
            Px := Bmp.Canvas.Pixels[X, Y];
            if (GetRValue(Px) > 180) and (GetGValue(Px) < 100) and
               (GetBValue(Px) < 100) then
              Inc(RedHits);
          end;
        // Lock the expected ink: demand a real area of red-dominant
        // pixels, never settle for "not all black".
        Assert(RedHits > 500);
      finally
        Bmp.Free;
      end;
    end;
  finally
    Pdf.Free;
  end;
end;

ثابت (fixture) به اندازه ادعا اهمیت دارد. یک فایل PDF دست‌ساز به طول چند صد بایت با یک پر کردن Separation و نه هیچ چیز دیگر، هیچ ابهامی در مورد خروجی مورد انتظار باقی می‌گذارد؛ یک فایل واقعی کدهای بیشتری را اجرا می‌کند اما نمی‌تواند به شما بگوید کدام مرحله شکست خورده است. ما اکنون شمارش پیکسل‌های رنگ مورد انتظار را به عنوان حداقل استاندارد برای هر تست دود (smoke test) رندرینگ در نظر می‌گیریم، زیرا این تنها سبک ادعایی است که این نقص‌ها را آشکار کرد

رندر فضاهای رنگی spot و CIE بخشی از خط لوله اسناد بارگذاری‌شده در HotPDF Component برای دلفی و C++Builder در کنار موتور تابع، رندر سایه‌ها و مسیرهای خروجی بیت‌مپ نشان داده شده در بالا است