מאמר טכני

טבלאות חיפוש צבעים תלת-ממדיות (3D LUTs) ב-PDF עם פונקציות דגומות מטיפוס 0

פונקציות PDF (או PDF Functions) הן אחת הפינות השקטות יותר במפרט. רוב המפתחים נתקלים בהן פעם אחת, כדבר שהצללה צירית מטיפוס 2 זקוקה לו כדי להשתנות בין שני צבעים, ולא מביטים בהן שוב. חבל, מכיוון שמנגנון הפונקציות הוא מעריך (evaluator) קטן לשימוש כללי שהפורמט עושה בו שימוש חוזר להצללות, פונקציות העברה (transfer functions), פונקציות נקודת רשת של גווני ביניים, גווני הפרדה (separation tints) ועקומות העברה של מסיכה רכה. מבין ארבעת טיפוסי הפונקציות, טיפוס 0 הוא החזק ביותר והפחות מובן. זוהי פונקציה דגומה (sampled function): רשת רב-ממדית של ערכי פלט שהקורא מציב ביניהם באינטרפולציה. מכיוון שהרשת יכולה להחזיק כל מספר שתשים בה, פונקציה מטיפוס 0 יכולה לבטא מיפוי לא ליניארי שרירותי, שהוא בדיוק הצורה של טבלת חיפוש צבעים (color lookup table)

מאמר זה עובר על מילון טיפוס 0 כפי שמוגדר בתקן ISO 32000-1 בסעיף 7.10.2, ולאחר מציג את שני המקרים החשובים ביותר בצינור עבודה של מסמכים: טבלת LUT לתיקון צבע RGB-ל-RGB עם שלוש כניסות, והמרת גוון צבע ספוט (spot-color tint transform) עם כניסה אחת. אותו בונה פונקציות דגומות משרת את שניהם, וההבדל ביניהם הוא לחלוטין שאלה של כמה כניסות יש לרשת

פונקציה דגומה היא רשת שהקורא מבצע עבורה אינטרפולציה

פונקציה מטיפוס 0 ממפה וקטור בעל m כניסות לוקטור בעל n יציאות על ידי שמירת דגימות על רשת קבועה וביצוע אינטרפולציה ביניהן. תקן ISO 32000-1 §7.10.2 מפרט את המפתחות המתארים את הרשת הזו. /Domain מחזיק שני מספרים לכל כניסה, הגבול הנמוך והגבוה של כל ציר כניסה. /Range מחזיק שני מספרים לכל רכיב פלט. /Size הוא מערך של m מספרים שלמים המציין את מספר הדגימות לאורך כל ציר כניסה, כך שרשת של 12 דגימות לכל כיוון בשלושה ממדים היא בעלת /Size [12 12 12] ושומרת 1,728 נקודות רשת. /BitsPerSample קובע את הדיוק של כל ערך שמור; HotPDF מקבל 1, 2, 4, 8, 12, 16, 24 ו-32 סיביות, בהתאמה לערכים שטבלה 38 מאפשרת

זרם הדגימות נקרא בסדר קבוע. ממד הכניסה הראשון משתנה הכי מהר, ואז השני, וכן הלאה, ובכל נקודת רשת רכיבי הפלט ה-n נשמרים לפי הסדר. עבור טבלת RGB-ל-RGB זה שלושה בתים לכל נקודת רשת בשמונה סיביות, המסודרים כפלט אדום, פלט ירוק, פלט כחול, כשהסריקה מתחילה על פני הכניסה של האדום תחילה. שני מפתחות נוספים ממפים את העולם הרציף על גבי רשת המספרים השלמים. /Encode ממפה כל כניסה ממרווח ה-/Domain שלה לטווח אינדקס הדגימות 0 עד Size[i] - 1, ו-/Decode ממפה את המספרים השלמים הגולמיים השמורים בחזרה למרווחי /Range. כאשר אתה משאיר אותם בברירת המחדל שלהם, כניסה המשתרעת על [0 1] נוחתת בצורה נקייה על הרשת המלאה ובית שמור של 255 מפענח לקצה העליון של טווח הפלט שלו, וזה מה שטבלת LUT מנורמלת ל-[0,1] רוצה

אנטומיה של HotPDF לפונקציה דגומה PDF Type 0: Domain ו-Encode ממפים כל קלט אל מדדי רשת, הדגימות מיורבבות, ואז Decode ו-Range מתאימים את סקאלת הפלטים
הקורא מאתחל רשת דגימות סדירה שצורתה באה מ-Size, וסורקת את הקלט הראשון במהירות הגבוהה ביותר. Encode ו-Decode קושרים את העולם הרציף אל השלמים האגורים

סדר 1 מול סדר 3

בין נקודות הרשת הקורא צריך לבצע אינטרפולציה, והמפתח /Order בוחר כיצד. /Order 1 הוא אינטרפולציה רב-ליניארית: ליניארית לאורך ציר אחד, דו-ליניארית על פני שניים, ותלת-ליניארית על פני שלושה. זה מהיר, זה בדיוק מה שהחומרה ברוב המציגים עושה, ועבור המרת צבע חלקה זה בדרך כלל בלתי ניתן להבחנה מכל דבר מורכב יותר. /Order 3 מבקש אינטרפולציית cubic-spline, המתאימה עקומה חלקה יותר דרך הדגימות במחיר של יותר עבודה ואזור תמיכה רחב יותר סביב כל נקודה מוערכת

הפשרה היא צפיפות הרשת מול חלקות העקומה. סדר מעוקב (cubic) מוכיח את עצמו כאשר הרשת היא גסה ולמיפוי יש קימור נראה לעין, מכיוון שקו ישר בין שתי דגימות מרוחקות יכול לשטח עקומת גוון באופן שהעין תופסת במעברי צבע. ברגע שהרשת צפופה, המקש המקטעים קצר מספיק כדי שאינטרפולציה ליניארית תעקוב אחר העקומה בצורה הדוקה וסדר מעוקב יוסיף מעט מאוד. כלל מעשי הוא לבחור ב-/Order 3 רק עם רשתות קטנות או המרות תלולות, ובמקרים אחרים להשאיר אותו בברירת המחדל הליניארית. שים לב ש-/Order חל על פונקציות מטיפוס 0 בלבד, ו-HotPDF דוחה כל ערך שאינו 1 או 3

ה-3D LUT: שלוש כניסות, שלושה פלטים

תיקון צבע של RGB-ל-RGB הוא מקרה ספרותי קלאסי עבור רשת בעלת שלוש כניסות, ה-3D LUT הקלאסי המשמש בדירוג צבעים (color grading) והתאמת מכשירים. כל ציר של הקובייה הוא ערוץ כניסה אחד, כל נקודת רשת שומרת את שלשת ה-RGB המתוקנת עבור קואורדינטת כניסה זו, והקורא מבצע אינטרפולציה תלת-ליניארית על דגימות הפינה סביב כל צבע נכנס. שלוש כניסות הן בלתי נמנעות כאן מכיוון שהאדום המתוקן יכול להיות תלוי בירוק ובכחול שנכנסו, ולא רק באדום שנכנס; עקומה לכל ערוץ אינה יכולה לבטא השפעה הדדית בין ערוצים (crosstalk), אך קובייה יכולה

HotPDF בונה את זרם טיפוס 0 דרך RegisterSampledFunction, המקבל את ה-/Domain,‏ /Range,‏ /Size,‏ /BitsPerSample, ואת בתיי הדגימה ישירות ומחזיר את אובייקט הפונקציה. עבור קובייה מנורמלת סטנדרטית אתה מעביר גבולות [0,1] בכל שלושת צירי הכניסה וכל שלושת הפלטים, גודל N x N x N, ואת טבלת הדגימות השטוחה. הבונה מאמת שספירת הבתים תואמת לרשת: עבור עומק מיושר לבתים הוא מצפה ל-OutputCount x (BitsPerSample div 8) x מכפלת הגדלים, ומעלה שגיאה אם המערך הוא באורך הלא נכון, כך שפסיעה (stride) שחושבה לא נכון נכשלת ברעש בזמן הרישום במקום להתרנדר כזבל מאוחר יותר

const
  N = 17;  // קובייה בגודל 17 x 17 x 17, רזולוציית ICC LUT הנפוצה
var
  LutFn: THPDFStreamObject;
  Samples: TBytes;
begin
  // מלא את Samples בנקודות רשת N*N*N, שלושה בתים לכל אחת (פלט R,G,B),
  // כאשר קלט האדום משתנה הכי מהר. בנה את השלשה המתוקנת לכל
  // קואורדינטת רשת באמצעות ההמרה המנוהלת על ידי ICC, ואז אחסן אותה
  SetLength(Samples, N * N * N * 3);
  BuildCorrectedCube(Samples, N);   // your color-managed fill

  LutFn := Pdf.RegisterSampledFunction(
    [0,1, 0,1, 0,1],   // /Domain: שלושה צירי קלט ב-[0,1]
    [0,1, 0,1, 0,1],   // /Range:  שלושה ערוצי פלט ב-[0,1]
    [N, N, N],         // /Size:   רזולוציית הקובייה לכל ציר
    8,                 // /BitsPerSample
    Samples,
    1);                // /Order 1 = trilinear
end;

התקינות הצבעונית (colorimetric) של הקובייה תלויה באופן שבו אתה ממלא אותה, ולא בפונקציית ה-PDF. הנתיב הנכון הוא לחשב כל נקודת רשת דרך המרת ICC מנוהלת, אותו מנוע שמניע הגהת מסך (soft-proof), כך שהמספרים ברשת יביעו משהו מול פרופיל מקור ויעד מוגדרים. רשום את הפרופילים המגבילים את ההמרה עם RegisterICCProfile, המתעד מרחב צבע ICCBased (בעל 1, 3 או 4 רכיבים) ומחזיר שם משאב שאתה יכול לחבר לתוכן שספק ה-LUT מזין. הפונקציה מטיפוס 0 נושאת את טבלת האינטרפולציה; פרופיל ה-ICC נושא את המשמעות של נקודות הקצה

המקרה החד-ממדי: המרת גוון צבע ספוט

מרחבי צבע של הפרדה (Separation color spaces) נשענים על אותו מנגנון עבור עבודה שונה לחלוטין. מרחב הפרדה, המוגדר בתקן ISO 32000-1 §8.6.6.4, מייצג צבען (colorant) יחיד, דיו ספוט כגון Pantone או לכה, על ידי צימוד שם עם המרת גוון (tint transform): פונקציה שממפה את ערך הגוון החד-ממדי, 0 עבור ללא דיו ועד 1 עבור דיו מלא, למרחב צבע חלופי שהמכשיר יכול לרנדר בפועל, בדרך כלל CMYK. המרת הגוון הזו היא לעיתים קרובות פונקציה מטיפוס 0, וכעת לרשת יש בדיוק ציר כניסה אחד

זהו הניגוד הברור ל-3D LUT. דיו ספוט הוא בעל דרגת חופש אחת, ולכן המרת הגוון שלו זקוקה לכניסה אחת והרשת היא קו של דגימות, שכל אחת מהן מחזיקה בערך CMYK (או ערך חלופי אחר) ברמת הגוון ההיא. קוביית ה-RGB זקוקה לשלוש כניסות מכיוון שהתחום שלה הוא תלת-ממדי והערוצים משפיעים זה על זה. אותו סוג פונקציה, אותם כללי אינטרפולציה, ממדיות שונה; המפרט משתמש שוב במעריך יחיד ומאפשר ל-/Size להחליט אם אתה הולך על קו, מישור או קובייה. HotPDF עוטף את כל ההפרדה ב-RegisterSeparationLUT, הבונה את המרת הגוון מטיפוס 0 בעלת הכניסה האחת מתוך מערך בתים שטוח באופן פנימי ומחזיר את שם משאב מרחב הצבע

var
  SpotCS: AnsiString;
begin
  // ארבעה בתים של פלט CMYK לכל נקודת רשת גוון, תחום הגוון [0..1]
  // כאן 0% דיו -> הכול אפס, 100% דיו -> בניית spot עשירה,
  // עם שתי מדרגות פנימיות; טרנספורמציית הגוון מבצעת אינטרפולציה ביניהן
  SpotCS := Pdf.RegisterSeparationLUT(
    'PANTONE 286 C',         // colorant name
    'DeviceCMYK',            // מרחב צבע חלופי
    [  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]);   // גוון 1.00 -> בניית דיו מלאה
  // השתמש ב-SpotCS עם SetFillColorSpace / SetFillColor בדף
end;

ספירת הדגימות חייבת להיות מספר שלם של נקודות רשת: כפולה חיובית של מספר הרכיבים של המרחב החלופי, ולפחות שתי נקודות כדי שיהיה מקטע לביצוע אינטרפולציה. העבר שלושה בתים לנקודה מול חלופת CMYK והקריאה תדחה זאת, אותו אימות הגנתי שבונה התלת-ממד מיישם, וזה מה שאתה רוצה מפונקציה שאחרת תיכשל בשקט בזמן ההדפסה

HotPDF: תיקון צבע RGB ל-RGB המצויר כקוביית LUT תלת-ממדית: כל ציר הוא ערוץ קלט אחד ושמונה פינות מקיפות מזינות מיזוג טריליניארי עבור הצבע הנכנס
כל פינה אוגרת את השלשה המתוקנת שחושבה תחת המרה בניהול ICC. ערבוב טריליניארי משחזר את הצליבה בין ערוצים שעקומות-לכל-ערוץ אינן יכולות לבטא

היכן שאותו מנגנון מופיע שוב

ברגע שאתה רואה את טיפוס 0 כטבלת אינטרפולציה כללית, שתי תכונות נוספות של בקרת מכשירים מפסיקות להיראות כמו מקרים מיוחדים. פונקציית העברה (transfer function) מתאימה את ערכי הרכיבים בדרכם למכשיר הפלט, והיא פשוט פונקציה לכל ערוץ; HotPDF רושם אותה כ-ExtGState דרך RegisterTransferFunctionState, המקבל פונקציה משולבת אחת או מערך של פונקציות לכל ערוץ. מכיוון שהפונקציות הללו הן אובייקטי פונקציה רגילים, אתה יכול להעביר לה את אובייקט ה-THPDFStreamObject ש-RegisterSampledFunction מחזיר ולהניע עקומת העברה מטבלה דגומה במקום מנוסחה

var
  ToneFn: THPDFStreamObject;
  GsName: AnsiString;
begin
  // עקומת גוון מדוגמת בעלת קלט יחיד ופלט יחיד ב-[0,1]
  ToneFn := Pdf.RegisterSampledFunction(
    [0,1], [0,1], [256], 8, ToneCurveBytes, 1);

  // החל אותה על כל הערוצים כפונקציית העברה משולבת /TR2
  GsName := Pdf.RegisterTransferFunctionState(ToneFn, []);
  // בחר את GsName בדף לפני ציור התוכן המושפע
end;

יצירת שחור (Black generation) והסרת צבע תחתון (undercolor removal) יושבות באותה משפחה. כאשר מכשיר ממיר RGB ל-CMYK הוא מחליט כמה מרכיב האפור לשאת כדיו שחור, והמפרט מביע את ההחלטה הזו כפונקציה, ערכי /BG2 ו-/UCR2 של מילון מצב-גרפי (graphics-state dictionary), שכל אחד מהם הוא עקומה בעלת כניסה בודדת מהאפור המחושב לכמות שחורה. אלו הן פונקציות מטיפוס 0 גם כאשר אתה רוצה עקומה נמדדת ולא עקומה אנליטית, הבנויות באותו אופן דרך RegisterSampledFunction ומונחות במצב הגרפי. הלקח שראוי לזכור הוא שפונקציית ה-PDF היא לעולם לא המקום שבו מתרחש ניהול הצבע; זוהי טבלת החיפוש הנושאת החלטה שקיבלת עם מנוע צבע אמיתי, וטיפוס 0 הוא סוג הפונקציה היחיד שגמיש מספיק כדי לשאת כל החלטה שהיא

עבור התמונה הרחבה יותר של אופן פליטת גופנים, תמונות ומשאבי צבע למסמך גמור, ראה את המדריך שלנו על פלט דוחות עם גופנים ותמונות. כאשר הפלט חייב לשרוד בדיקת תאימות לארכיון או להדפסה, חוקי מרחב הצבע וכוונת הפלט (output-intent) המכוסים במדריך אימות ה-PDF/A, PDF/X, ו-PDF/UA קובעים אילו פונקציות מותרות וכיצד יש לתייג צבעי מכשיר. כל זה נשלח בתוך HotPDF Delphi Component עבור Delphi ו-C++Builder, לצד ממשקי ה-API להצללה, ICC והפרדה הנבנים על אותה ליבה של טיפוס 0

HotPDF: טרנספורמציית גוון Type 0 עם קלט אחד עבור colorant מקומי: קו של דגימות גוון, כל אחת מחזיקה ערך DeviceCMYK עבור המרחב החלופי
דיו spot יש לו מעלה חופשית אחת, ולכן הרשת שלו מתמוטטת לקו של דגימות. אותו מעריך מהלך את הקו הזה, מישור, או קוביית ה-RGB