יסודות: פיקסלים, נתיבים וצבע#
לפני כל ספרייה, ארבעה רעיונות נושאים את כל הנושא: ההבדל בין רסטר לווקטור, מערכת הקואורדינטות שבה חי הציור, מהו למעשה צבע כאשר צריך לאחסן אותו, וכיצד שני צבעים משתלבים כאשר אחד שקוף חלקית. כל פרק שאחרי זה נשען על אלה בלי להסביר אותם מחדש. אף אחד מהם אינו ייחודי ל־Perl, אך כל הדוגמאות הן Perl, מפני שהמטרה היא לגלם את הרעיונות באופן מוחשי בקוד שאפשר להריץ.
רסטר לעומת ווקטור#
תמונת רסטר היא רשת של תאים צבועים. יש לה רוחב וגובה קבועים בפיקסלים, וכל פיקסל מחזיק צבע. הגדלתה מעבר לרזולוציה שלה אינה ממציאה דבר: התאים פשוט נעשים גדולים יותר. תצלומים, צילומי מסך וכל דבר בעל גוון רציף הם רסטרים. Imager, GD ו־ImageMagick הם כלי רסטר.
תמונת ווקטור היא אוסף של צורות המתוארות מתמטית: ”קו מכאן לשם“, ”מעגל ברדיוס זה“, ”מלא נתיב זה“. אין רזולוציה עד שמציירים אותה, ולכן היא מותאמת לכל גודל בנקיות. גופנים, לוגואים ותרשימים הם ווקטורים. Cairo ו־SVG הם כלי ווקטור.
השניים נפגשים ברסטריזציה: הפיכת תיאור ווקטורי לפיקסלים. ספריית ווקטור עדיין חייבת להחליט אילו פיקסלים קו אלכסוני נוגע בהם, וההחלטה הזו היא האלגוריתם הראשון בפרק הבא. כל דבר שמציירים מסתיים כפיקסלים על מסך; השאלה היא רק אם מתחייבים לרזולוציה מוקדם (רסטר) או מאוחר (ווקטור).
רסטר ב־Perl הוא, בצורתו הפשוטה ביותר, מערך של שורות:
my $w = 4;
my $h = 3;
# each pixel is [r, g, b], each channel 0..255
my @img = map { [ map { [0, 0, 0] } 1 .. $w ] } 1 .. $h;
$img[1][2] = [255, 0, 0]; # row 1, column 2, red
תמונת המערך המקונן הזו היא כל מה שרסטר הוא. ספריות אמיתיות מאחסנות את אותה רשת בחוצץ C ארוז לשם מהירות, אך המודל המנטלי אינו משתנה: פיקסל ממוען על ידי שורה ועמודה שלמות, והוא מחזיק צבע.
מערכת הקואורדינטות#
קואורדינטות גרפיקה אינן קואורדינטות של שיעור מתמטיקה. ראשית הצירים (0, 0) היא הפינה השמאלית־עליונה, x גדל ימינה, ו־y גדל כלפי מטה. זה מפיל את כולם פעם אחת: y גדול יותר נמצא נמוך יותר על המסך, לא גבוה יותר.
# (0,0) top-left x ->
# +-----------------
# | (0,0) (w-1, 0)
# y |
# | |
# v | (0, h-1) (w-1, h-1)
פיקסל הוא ריבוע קטן, ויש עמימות מתמשכת לגבי השאלה אם הקואורדינטה (3, 5) מכנה את פינת הריבוע או את מרכזו. ספריות רסטר בדרך כלל מתייחסות לקואורדינטות שלמות כמרכזי פיקסלים; ספריות ווקטור מתייחסות אליהן כקווי הרשת שבין הפיקסלים, ולכן קו אנכי ברוחב יחידה אחת ב־x = 3 יכול לצאת פרוש על שתי עמודות פיקסלים בעוצמה חצי. כאשר קו דק או קצה מלבן נראה מטושטש או סטוי בפיקסל אחד, זו כמעט תמיד הסיבה, והתיקון הוא היסט של חצי פיקסל. הפרקים שנתקלים בזה אומרים זאת.
גדלים ומיקומים הם מספרים שלמים במרחב הרסטר ומספרים ממשיים במרחב הווקטור. זה ההבדל המעשי שמרגישים: ב־Imager מגדירים את הפיקסל (120, 47); ב־Cairo אפשר לנוע אל (120.5, 47.25) והרסטרייזר מחשב את הכיסוי.
צבע: מה פיקסל מחזיק#
צבע על מסך הוא שלושה מספרים: כמה אור אדום, ירוק וכחול לפלוט. הוסיפו את שלושתם בעוצמה מלאה ותקבלו לבן; השאירו את כולם כבויים ותקבלו שחור. זהו מודל ה־RGB, והוא חיבורי מפני שמוסיפים אור, לא מערבבים צבע.
כל ערוץ מאוחסן כמעט תמיד ב־8 סיביות, כלומר 0 .. 255 לערוץ, וצבע הוא שלושה בתים. זהו סימון ה־hex #RRGGBB מן הרשת: #ff0000 הוא (255, 0, 0), אדום טהור.
sub rgb_to_hex {
my ($r, $g, $b) = @_;
return sprintf "#%02x%02x%02x", $r, $g, $b;
}
print rgb_to_hex(255, 128, 0), "\n"; # #ff8000, an orange
RGB הוא האופן שבו פיקסלים מאוחסנים, אך הוא מודל נורא עבור אדם הבוחר צבע. ”הפוך את זה מעט יותר כתום אך שמור על אותה בהירות“ אינו מהלך מובן מאליו ב־RGB. HSV (גוון, רוויה, ערך) הוא המודל לכך: גוון הוא המיקום על גלגל הצבעים במעלות 0 .. 360 (0 אדום, 120 ירוק, 240 כחול), רוויה היא כמה חי לעומת אפור 0 .. 1, וערך הוא כמה בהיר 0 .. 1. סיבוב הגוון תוך שמירה על הרוויה והערך צועד על הקשת בחיוניות ובהירות קבועות.
כל בורר־צבעים וכלי מדרג עובד ב־HSV או בקרוב־משפחה שלו, ואז ממיר ל־RGB לשם אחסון. כדאי שההמרה תהיה גלויה לעין, מפני שתממשו אותה מחדש יותר מפעם אחת:
# h in 0..360, s and v in 0..1 -> (r, g, b) in 0..255
sub hsv_to_rgb {
my ($h, $s, $v) = @_;
my $c = $v * $s; # chroma
my $hp = $h / 60;
my $x = $c * (1 - abs($hp % 2 - 1 + ($hp - int $hp)));
my ($r, $g, $b) =
$hp < 1 ? ($c, $x, 0) :
$hp < 2 ? ($x, $c, 0) :
$hp < 3 ? (0, $c, $x) :
$hp < 4 ? (0, $x, $c) :
$hp < 5 ? ($x, 0, $c) :
($c, 0, $x);
my $m = $v - $c;
return map { int(($_ + $m) * 255 + 0.5) } ($r, $g, $b);
}
my @rainbow = map { [ hsv_to_rgb($_, 1, 1) ] } 0, 60, 120, 180, 240, 300;
# red, yellow, green, cyan, blue, magenta
ישנם מרחבים נוספים שתפגשו: גווני־אפור (ערוץ אחד, בהירות), CMYK (חיסורי, להדפסה), ומרחבים אחידים תפיסתית כמו CIELAB שבהם צעדים מספריים שווים נראים כצעדים תפיסתיים שווים. לציור על מסך בשני ממדים, RGB לאחסון ו־HSV לבחירה מכסים כמעט הכול.
אלפא והרכבה#
ערוץ רביעי, אלפא, מתעד אטימות: 0 שקוף לחלוטין, 255 (או 1.0) אטום לחלוטין. פיקסל עם אלפא הוא RGBA. אלפא הוא מה שמאפשר לצייר תמונה אחת מעל אחרת ולגרום לחלקים השקופים של העליונה להיראות דרכה.
שילוב פיקסל חזית מעל פיקסל רקע הוא הרכבה, והכלל הסטנדרטי הוא source-over. עם אלפא כשבר 0 .. 1:
# composite one RGBA pixel over another (all channels 0..255,
# alpha treated as 0..1 for the blend)
sub over {
my ($fg, $bg) = @_;
my $a = $fg->[3] / 255; # source alpha
my @out = map {
int($fg->[$_] * $a + $bg->[$_] * (1 - $a) + 0.5)
} 0 .. 2;
return \@out;
}
my $red_half = [255, 0, 0, 128]; # red at 50% opacity
my $white = [255, 255, 255, 255];
my $result = over($red_half, $white);
print "@$result\n"; # 255 128 128, a pink
אדום באטימות חצי מעל לבן נותן ורוד, בדיוק כפי שהייתם מצפים שצבע־מדולל־במים ייראה. הנוסחה האחת הזו, המיושמת לכל פיקסל, היא האופן שבו כל שכבה, משיכת מכחול וספרייט בעל קצוות רכים נוחתים על הקנבס שמתחתיהם. כשמערמים שכבות רבות שקופות־למחצה, מיישמים אותה שוב ושוב, מלמטה למעלה.
הדקות שנושכת מאוחר יותר היא אלפא מוכפלת־מראש: אחסון כל ערוץ צבע כשהוא כבר מוכפל באלפא שלו. היא מוזילה הרכבה חוזרת ומתקנת סינון בקצוות, וזו הסיבה שבתי הפיקסל הגולמיים של ספרייה לעיתים אינם ה־RGB שאתם מצפים לו. Cairo משתמש באלפא מוכפלת־מראש באופן פנימי; פרק Cairo מציין היכן זה מופיע.
רעיון התלת־ממד היחיד, מוזכר ונדחה#
תשמעו על ה־Z-buffer ברגע שתלת־ממד עולה, וכדאי להקדיש לו פסקה אחת כאן כדי שהמונח לא יהיה תעלומה. בדו־ממד, ”מה נמצא למעלה“ נקבע לפי סדר הציור: הדבר האחרון שצויר מנצח, וזו בדיוק הרכבת ה־source-over לעיל. בתלת־ממד, לאובייקטים יש עומק (קואורדינטת z), והדבר הקרוב ביותר לצופה צריך לנצח ללא תלות בסדר הציור. Z-buffer הוא רשת שנייה, בגודל התמונה, המחזיקה את העומק של המשטח הקרוב ביותר שנראה עד כה בכל פיקסל; לפני ציור פיקסל בודקים אם המשטח החדש קרוב יותר מהמתועד, ומדלגים עליו אם לא.
זהו הרעיון כולו, וזה עד כמה מדריך דו־ממד זה מתקדם איתו. חציצת עומק, היטל, ושאר הממד השלישי שייכים להמשך המתוכנן. בשני ממדים, סדר הציור הוא חוצץ העומק שלכם.
לאן זה הולך הלאה#
כעת יש לכם את אוצר המילים שבו מדבר שאר המדריך: רסטר וווקטור, קואורדינטות שמאליות־עליונות, RGB ו־HSV, אלפא והרכבת source-over. הפרק הבא מוציא את אוצר המילים הזה על אלגוריתמי הציור הקלאסיים, ממומשים מאפס, כך שכאשר ספרייה מציירת קו או ממלאת מצולע תדעו בדיוק מה היא עושה עבורכם.