בד ציור עם DPI גבוה

מבוא

מסכי HiDPI נהדרים, הם גורמים לכל דבר להיראות חלק יותר ונקי יותר. אבל הם גם מציגים מפתחים לאתגרים חדשים. במאמר הזה נבחן את האתגרים הייחודיים של ציור תמונות בקנבס בהקשר של מסכים ברזולוציה גבוהה (HiDPI).

המאפיין devicePixelRatio

נתחיל מההתחלה. לפני שהיו מסכים עם רזולוציית HiDPI, פיקסל היה פיקסל (אם מתעלמים לרגע מהגדלת התצוגה והשינוי של קנה המידה), וזה היה הכול. לא היה צורך לשנות שום דבר. אם הגדרתם משהו ברוחב 100px, זה כל מה שצריך. לאחר מכן, החלו להופיע מכשירי HiDPI ניידים ראשונים עם המאפיין devicePixelRatio הקצת מסתורי באובייקט window, שזמין לשימוש בבקשות מדיה. המאפיין הזה אפשר לנו להבין את היחס בין ערכי הפיקסלים (שנקראים 'ערך הפיקסל הלוגי') ב-CSS, לבין מספר הפיקסלים האמיתי שהמכשיר ישתמש בהם בתהליך הרינדור. במקרה של מכשיר iPhone 4S, עם devicePixelRatio של 2, הערך הלוגי של 100px שווה לערך של 200px במכשיר.

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

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

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

function setupCanvas(canvas) {
  // Get the device pixel ratio, falling back to 1.
  var dpr = window.devicePixelRatio || 1;
  // Get the size of the canvas in CSS pixels.
  var rect = canvas.getBoundingClientRect();
  // Give the canvas pixel dimensions of their CSS
  // size * the device pixel ratio.
  canvas.width = rect.width * dpr;
  canvas.height = rect.height * dpr;
  var ctx = canvas.getContext('2d');
  // Scale all drawing operations by the dpr, so you
  // don't have to worry about the difference.
  ctx.scale(dpr, dpr);
  return ctx;
}

// Now this line will be the same size on the page
// but will look sharper on high-DPI devices!
var ctx = setupCanvas(document.querySelector('.my-canvas'));
ctx.lineWidth = 5;
ctx.beginPath();
ctx.moveTo(100, 100);
ctx.lineTo(200, 200);
ctx.stroke();