مقاله فنی

جداول جستجوی رنگ سه‌بعدی در PDF با توابع نمونه‌برداری‌شده Type 0

توابع PDF یکی از بخش‌های کمتر شناخته‌شده در مشخصات این فرمت هستند. بیشتر توسعه‌دهندگان تنها یک‌بار با آن‌ها مواجه می‌شوند؛ به‌عنوان ابزاری که یک سایه‌زنی محوری (axial shading) از نوع Type 2 برای ترکیب و تغییر پیوسته بین دو رنگ به آن نیاز دارد، و دیگر هرگز به سراغ آن‌ها نمی‌روند. این مایه تأسف است، زیرا مکانیزم توابع، یک ارزیاب کوچک چندمنظوره است که این فرمت از آن برای سایه‌زنی‌ها (shadings)، توابع انتقال (transfer functions)، توابع هاف‌تون (halftone spot functions)، تینت‌های رنگ‌های تفکیکی (separation tints) و منحنی‌های انتقال ماسک نرم (soft-mask transfer curves) استفاده مجدد می‌کند. از میان چهار نوع تابع موجود، Type 0 قدرتمندترین و در عین حال ناشناخته‌ترین آن‌هاست. این یک تابع نمونه‌برداری‌شده (sampled function) است: یک شبکه چندبعدی از مقادیر خروجی که نرم‌افزار خواننده (reader) بین آن‌ها درون‌یابی انجام می‌دهد. از آنجایی که این شبکه می‌تواند هر عددی که در آن قرار می‌دهید را در خود نگه دارد، تابع Type 0 قادر است یک نگاشت غیرخطی دلخواه را بیان کند، که دقیقاً همان ساختار یک جدول جستجوی رنگ (color lookup table) است

این مقاله دیکشنری Type 0 را همان‌طور که ISO 32000-1 در بخش ۷.۱۰.۲ تعریف می‌کند، بررسی کرده و سپس دو موردی که بیشترین اهمیت را در یک خط لوله پردازش سند (document pipeline) دارند، نشان می‌دهد: یک LUT تصحیح رنگ RGB به RGB با سه ورودی، و یک تبدیل تینت رنگ نقطه‌ای (spot-color tint transform) با یک ورودی. یک سازنده تابع نمونه‌برداری‌شده برای هر دو مورد کار می‌کند، و تفاوت بین آن‌ها کاملاً به این موضوع بستگی دارد که شبکه چند ورودی دارد

تابع نمونه‌برداری‌شده شبکه‌ای است که نرم‌افزار خواننده آن را درون‌یابی می‌کند

یک تابع Type 0 با ذخیره نمونه‌ها در یک شبکه منظم و درون‌یابی بین آن‌ها، یک بردار با m ورودی را به یک بردار با n خروجی نگاشت می‌کند. استاندارد ISO 32000-1 بخش ۷.۱۰.۲ کلیدهایی را فهرست می‌کند که این شبکه را توصیف می‌کنند. کلید /Domain برای هر ورودی دو عدد را در خود نگه می‌دارد: حد پایین و حد بالای هر محور ورودی. کلید /Range دو عدد را برای هر مؤلفه خروجی نگه می‌دارد. کلید /Size آرایه‌ای از m عدد صحیح است که تعداد نمونه‌ها را در امتداد هر محور ورودی مشخص می‌کند، بنابراین شبکه‌ای که در هر بعد از سه بعد خود دوازده نمونه دارد دارای /Size [12 12 12] بوده و ۱,۷۲۸ نقطه شبکه را ذخیره می‌کند. کلید /BitsPerSample دقت هر مقدار ذخیره‌شده را تنظیم می‌کند؛ HotPDF مقادیر ۱، ۲، ۴، ۸، ۱۲، ۱۶، ۲۴ و ۳۲ بیتی را می‌پذیرد که با مقادیر مجاز در جدول ۳۸ مطابقت دارد

جریان نمونه با ترتیب ثابتی خوانده می‌شود. اولین بعد ورودی سریع‌تر از همه تغییر می‌کند، سپس بعد دوم، و به همین ترتیب؛ و در هر نقطه شبکه، n مؤلفه خروجی به ترتیب ذخیره می‌شوند. برای یک جدول RGB به RGB که در هشت بیت شامل سه بایت به ازای هر نقطه شبکه است، به ترتیب خروجی-قرمز، خروجی-سبز، و خروجی-آبی چیده شده و ابتدا روی ورودی قرمز پیمایش می‌شود. دو کلید دیگر، دنیای پیوسته را به شبکه اعداد صحیح نگاشت می‌کنند. کلید /Encode هر ورودی را از بازه /Domain خود به بازه اندیس‌نمونه‌ها (از ۰ تا Size[i] - 1) نگاشت کرده، و /Decode اعداد صحیح خام ذخیره‌شده را دوباره به بازه‌های /Range باز می‌گرداند. وقتی آن‌ها را روی مقادیر پیش‌فرض خود رها می‌کنید، ورودی که بازه [0 1] را پوشش می‌دهد به‌طور تمیز روی کل شبکه قرار می‌گیرد و یک بایت ذخیره‌شده با مقدار ۲۵۵ به بالاترین حد از دامنه خروجی خود رمزگشایی می‌شود، که دقیقاً همان چیزی است که یک LUT رنگی نرمال‌شده در بازه [0,1] به آن نیاز دارد

بررسی Order 1 در مقابل Order 3

نرم‌افزار خواننده بین نقاط شبکه باید درون‌یابی کند و کلید /Order نحوه این کار را انتخاب می‌کند. مقدار /Order 1 درون‌یابی چندخطی (multilinear) است: خطی در یک محور، دوخطی در دو محور، و سه‌خطی در سه محور. این روش سریع است، دقیقاً همان کاری است که سخت‌افزار در بیشتر نرم‌افزارهای نمایش‌دهنده انجام می‌دهد، و برای یک تبدیل رنگ نرم (smooth) معمولاً از روش‌های پیچیده‌تر قابل تشخیص نیست. مقدار /Order 3 درون‌یابی اسپلاین-مکعبی (cubic-spline) را درخواست می‌کند که یک منحنی نرم‌تر را از بین نمونه‌ها عبور می‌دهد، اما به قیمت پردازش بیشتر و ناحیه پشتیبانی وسیع‌تر در اطراف هر نقطه ارزیابی‌شده

بده‌بستان در اینجا تراکم شبکه در برابر نرمی منحنی است. مرتبه مکعبی (Cubic) زمانی ارزش خود را نشان می‌دهد که شبکه درشت (coarse) باشد و نگاشت دارای انحنای قابل‌مشاهده‌ای باشد، زیرا یک خط راست بین دو نمونه دور از هم می‌تواند منحنی رنگ (tone curve) را به‌گونه‌ای تخت کند که چشم آن را در گرادیان‌ها تشخیص دهد. وقتی شبکه متراکم شد، قطعات به اندازه‌ای کوتاه هستند که درون‌یابی خطی، منحنی را از نزدیک دنبال می‌کند و مرتبه مکعبی فایده چندانی ندارد. یک قانون عملی این است که فقط در شبکه‌های کوچک یا تبدیل‌های پرشیب به سراغ /Order 3 بروید، و در غیر این صورت آن را در حالت پیش‌فرض خطی رها کنید. توجه داشته باشید که /Order فقط برای توابع Type 0 اعمال می‌شود و HotPDF هر مقداری غیر از ۱ یا ۳ را رد می‌کند

جدول جستجوی سه‌بعدی (3D LUT): سه ورودی، سه خروجی

تصحیح رنگ RGB به RGB، نمونه‌ای کلاسیک برای یک شبکه سه ورودی است؛ همان 3D LUT معروفی که در درجه‌بندی رنگ و تطبیق دستگاه‌ها استفاده می‌شود. هر محور این مکعب یک کانال ورودی است، هر نقطه از شبکه سه‌گانه RGB تصحیح‌شده را برای آن مختصات ورودی ذخیره می‌کند، و نرم‌افزار خواننده، نمونه‌های گوشه در اطراف هر رنگ ورودی را به‌صورت سه‌خطی درون‌یابی می‌کند. داشتن سه ورودی در اینجا اجتناب‌ناپذیر است زیرا مقدار قرمز تصحیح‌شده می‌تواند به سبز و آبی ورودی نیز بستگی داشته باشد و نه فقط به قرمز ورودی؛ یک منحنی تک‌کاناله نمی‌تواند تداخل کانال‌ها (channel crosstalk) را بیان کند، اما یک مکعب می‌تواند

کامپوننت HotPDF جریان Type 0 را از طریق RegisterSampledFunction می‌سازد که مقادیر /Domain، /Range، /Size، /BitsPerSample و بایت‌های نمونه را مستقیماً دریافت کرده و آبجکت تابع را بازمی‌گرداند. برای یک مکعب استاندارد نرمال‌شده، شما مرزهای [0,1] را روی هر سه محور ورودی و هر سه خروجی، اندازه N x N x N و جدول مسطح‌شده نمونه‌ها را ارسال می‌کنید. سازنده اعتبارسنجی می‌کند که تعداد بایت‌ها با شبکه مطابقت داشته باشد: برای عمق‌های تنظیم‌شده روی بایت، انتظار دارد مقدار آن برابر با OutputCount x (BitsPerSample div 8) x حاصل‌ضرب اندازه‌ها باشد، و اگر طول آرایه اشتباه باشد خطا (raise) تولید می‌کند، بنابراین یک گام (stride) که اشتباه محاسبه شده باشد، در زمان ثبت کردن فوراً با شکست مواجه می‌شود به جای آنکه در زمان رندر به شکل داده‌های نامعتبر (garbage) ظاهر شود

const
  N = 17;  // 17 x 17 x 17 cube, the common ICC LUT resolution
var
  LutFn: THPDFStreamObject;
  Samples: TBytes;
begin
  // Fill Samples with N*N*N grid points, 3 bytes each (R,G,B output),
  // red input varying fastest. Build the corrected triple for each
  // grid coordinate with your ICC-managed conversion, then store it.
  SetLength(Samples, N * N * N * 3);
  BuildCorrectedCube(Samples, N);   // your color-managed fill

  LutFn := Pdf.RegisterSampledFunction(
    [0,1, 0,1, 0,1],   // /Domain: three input axes on [0,1]
    [0,1, 0,1, 0,1],   // /Range:  three output channels on [0,1]
    [N, N, N],         // /Size:   the cube resolution per axis
    8,                 // /BitsPerSample
    Samples,
    1);                // /Order 1 = trilinear
end;

صحت کالریمتری (رنگ‌سنجی) مکعب در نحوه پر کردن آن است نه در تابع PDF. مسیر صحیح این است که هر نقطه از شبکه را از طریق یک تبدیل مدیریت‌شده با ICC محاسبه کنید، یعنی همان موتوری که تصحیح نرم‌افزاری (soft-proof) را اجرا می‌کند، به طوری که اعداد درون شبکه در برابر یک پروفایل مبدأ و مقصد مشخص معنا داشته باشند. پروفایل‌هایی را که تبدیل را محدود می‌کنند با RegisterICCProfile ثبت کنید، که یک فضای رنگی ICCBased (۱، ۳، یا ۴ مؤلفه‌ای) را رکورد می‌کند و یک نام منبع (resource name) بازمی‌گرداند که می‌توانید به محتوایی که توسط LUT تغذیه می‌شود متصل کنید. تابع Type 0 جدول درون‌یابی را با خود حمل می‌کند؛ در حالی که پروفایل ICC معنای نقاط انتهایی را در بر دارد

حالت یک‌بعدی: تبدیل تینت رنگ نقطه‌ای (spot-color tint transform)

فضاهای رنگی تفکیکی (Separation) به همین مکانیزم برای یک کار کاملاً متفاوت تکیه می‌کنند. فضای Separation که در ISO 32000-1 بخش ۸.۶.۶.۴ تعریف شده است، با جفت کردن یک نام با یک تبدیل تینت، یک رنگ‌دانه منفرد (مثل جوهر نقطه‌ای مانند Pantone یا وارنیش) را نشان می‌دهد: تبدیلی که مقدار تک‌بعدی تینت را، از ۰ برای بدون جوهر تا ۱ برای جوهر کامل، روی یک فضای رنگی جایگزین که دستگاه واقعاً قادر به رندر آن است (معمولاً CMYK) نگاشت می‌کند. آن تبدیل تینت غالباً یک تابع Type 0 است، و در اینجا شبکه دقیقاً یک محور ورودی دارد

این تفاوت واضح در مقایسه با 3D LUT است. یک جوهر نقطه‌ای دارای یک درجه آزادی است، بنابراین تبدیل تینت آن به یک ورودی نیاز دارد و شبکه، خطی از نمونه‌هاست که هر کدام مقدار CMYK (یا جایگزین دیگر) را در آن سطح تینت نگه می‌دارند. مکعب RGB به سه ورودی نیاز دارد زیرا دامنه آن سه‌بعدی است و کانال‌ها با یکدیگر تعامل دارند. همان نوع تابع، همان قواعد درون‌یابی، اما ابعاد متفاوت؛ مشخصات PDF از یک ارزیاب مجدداً استفاده می‌کند و به کلید /Size اجازه می‌دهد که تصمیم بگیرد آیا شما روی یک خط، یک صفحه یا یک مکعب حرکت می‌کنید. کامپوننت HotPDF کل عملیات تفکیک را در تابع RegisterSeparationLUT قرار داده است که به‌صورت داخلی تبدیل تینت یک‌ورودی Type 0 را از یک آرایه بایت مسطح می‌سازد و نام منبع فضای رنگی را بازمی‌گرداند

var
  SpotCS: AnsiString;
begin
  // Four CMYK output bytes per tint grid point, tint domain [0..1].
  // Here 0% ink -> all zero, 100% ink -> a rich spot build,
  // with two interior steps; the tint transform interpolates between.
  SpotCS := Pdf.RegisterSeparationLUT(
    'PANTONE 286 C',         // colorant name
    'DeviceCMYK',            // alternate color space
    [  0,   0,   0,   0,     // tint 0.00 -> 0,0,0,0
      90,  60,   0,   0,     // tint 0.33
     100,  80,   0,  10,     // tint 0.66
     100,  72,   0,  18]);   // tint 1.00 -> full ink build
  // Use SpotCS with SetFillColorSpace / SetFillColor on a page.
end;

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

جایی که همان مکانیزم دوباره نمایان می‌شود

زمانی که تابع Type 0 را به عنوان یک جدول درون‌یابی عمومی در نظر بگیرید، دو ویژگی دیگر کنترل دستگاه دیگر مانند موارد خاص به نظر نمی‌رسند. یک تابع انتقال (transfer function) مقادیر مؤلفه‌ها را در مسیر رفتن به دستگاه خروجی تنظیم می‌کند، و تنها یک تابع برای هر کانال است؛ کامپوننت HotPDF آن را به عنوان یک وضعیت ExtGState از طریق تابع RegisterTransferFunctionState ثبت می‌کند که این تابع هم می‌تواند یک تابع ترکیبی یا آرایه‌ای از توابع مجزا برای هر کانال را بپذیرد. از آنجا که این توابع، آبجکت‌های تابع معمولی هستند، می‌توانید دقیقاً همان THPDFStreamObject بازگشتی از RegisterSampledFunction را به آن بدهید و یک منحنی انتقال را به جای یک فرمول ریاضی، از یک جدول نمونه‌برداری‌شده کنترل کنید

var
  ToneFn: THPDFStreamObject;
  GsName: AnsiString;
begin
  // A single-input, single-output sampled tone curve on [0,1].
  ToneFn := Pdf.RegisterSampledFunction(
    [0,1], [0,1], [256], 8, ToneCurveBytes, 1);

  // Apply it to all channels as a combined /TR2 transfer function.
  GsName := Pdf.RegisterTransferFunctionState(ToneFn, []);
  // Select GsName on the page before drawing the affected content.
end;

تولید رنگ مشکی (Black generation) و حذف رنگ‌های زیرین (undercolor removal) در همان خانواده قرار دارند. هنگامی که یک دستگاه RGB را به CMYK تبدیل می‌کند، تصمیم می‌گیرد چه مقدار از مؤلفه خاکستری را به‌عنوان جوهر مشکی در نظر بگیرد و مشخصات PDF این تصمیم را به‌عنوان یک تابع با کلیدهای /BG2 و /UCR2 در دیکشنری گرافیک وضعیت (graphics-state) بیان می‌کند که هر یک دارای منحنی با یک ورودی از مقدار خاکستری محاسبه‌شده به مقدار رنگ مشکی هستند. زمانی که به جای یک منحنی تحلیلی (analytic) به یک منحنی اندازه‌گیری‌شده نیاز دارید، این‌ها نیز توابع Type 0 محسوب می‌شوند که به همان شیوه و از طریق RegisterSampledFunction ساخته شده و در وضعیت گرافیک (graphics state) قرار می‌گیرند. درسی که باید به خاطر سپرد این است که مدیریت رنگ هرگز در تابع PDF رخ نمی‌دهد؛ این تابع تنها یک جدول جستجو است که تصمیمی که شما توسط یک موتور رنگ واقعی اتخاذ کرده‌اید را با خود حمل می‌کند، و تابع Type 0 تنها نوع تابعی است که آن‌قدر انعطاف‌پذیر است تا بتواند هر تصمیمی را پوشش دهد

برای تصویر گسترده‌تری از نحوه خروج فونت‌ها، تصاویر و منابع رنگی در یک سند نهایی، آموزش گام‌به‌گام ما در مورد خروجی گزارش با فونت‌ها و تصاویر را مشاهده کنید. هنگامی که خروجی باید بررسی‌های preflight پیش از چاپ یا آرشیو را با موفقیت پشت سر بگذارد، قوانین فضای رنگی و intent خروجی که در راهنمای اعتبارسنجی PDF/A، PDF/X و PDF/UA پوشش داده شده‌اند، مشخص می‌کنند کدام‌یک از این توابع مجاز هستند و رنگ دستگاه چگونه باید تگ‌گذاری شود. تمام این قابلیت‌ها در کامپوننت HotPDF برای دلفی و سی‌پلاس‌پلاس‌بیلدر (C++Builder)، همراه با APIهای سایه‌زنی (shading)، رنگ ICC و تفکیک رنگ (separation) که بر پایه همین هسته Type 0 ساخته شده‌اند، عرضه می‌شود