Test Yourself!

These ten questions cover canvas behaviors that surprise experienced developers: a bitmap sized by CSS, a reset you did not ask for, paths that outlive a stroke, transforms that apply at different moments, shadows that ignore the scale, settings that are silently rejected, an image drawn too early, premultiplied alpha, a context hint given too late and a canvas that refuses to be read. Every snippet ran in Chrome 154 1 through the check.mjs script of Test Yourself!, which opens a page from disk, prints its console and saves a screenshot. Write down what each prints and why, then check Appendix H for the real results and the reasons.

Checking your answers

Run each page through check.mjs and read its console. Chrome also prints a warning that repeated getImageData() calls would be faster with willReadFrequently; it is advice, not an answer. For the second run of question 10, serve the folder and open the page in Chrome with its developer tools' console showing.

Running the questions from disk, then over HTTPShell
node check.mjs questions-1-5.html
node check.mjs questions-6-10.html
npx serve .           # then open http://localhost:3000/questions-6-10.html

Questions

questions-1-5.html: sizes, resets, paths, transforms and shadowsHTMLLive
<!-- 1. A canvas sized only by CSS. What do width and height print, and what shape
        does the circle take on screen? (Sections 2.1.1 and 2.1.3) -->
<canvas id="c1" style="width:600px; height:150px; outline:1px solid"></canvas>
<!-- 2. A red square, then width set to its own value, then a second square. What is
        left, and in what color? (Section 2.1.2) -->
<canvas id="c2" width="200" height="60"></canvas>
<!-- 3. Three lines in three colors after one beginPath(). What color is each line?
        (Section 2.2.1) -->
<canvas id="c3" width="200" height="60"></canvas>
<!-- 4. One rectangle as the current path and one as a Path2D, both built under
        translate(100, 0). Which contains (125, 25) after the reset? (Section 2.2.4) -->
<!-- 5. A shadow offset by 10 under scale(2). Which pixel columns does the visible
        shadow cover? (Section 2.3.3) -->
<canvas id="c5" width="200" height="60"></canvas>
<script>
  const px = (ctx, x, y) => ctx.getImageData(x, y, 1, 1).data.join(',');
  const x1 = c1.getContext('2d');
  x1.beginPath();  x1.arc(150, 75, 60, 0, 2 * Math.PI);  x1.fill();
  console.log('1:', c1.width, c1.height);
  const x2 = c2.getContext('2d');
  x2.fillStyle = 'red';  x2.fillRect(10, 10, 40, 40);
  c2.width = c2.width;
  x2.fillRect(60, 10, 40, 40);
  console.log('2:', px(x2, 30, 30), '|', px(x2, 80, 30));
  const x3 = c3.getContext('2d');  x3.lineWidth = 6;  x3.beginPath();
  ['blue', 'green', 'red'].forEach((color, i) => {
    x3.moveTo(10, 10 + i * 20);  x3.lineTo(190, 10 + i * 20);
    x3.strokeStyle = color;  x3.stroke();
  });
  console.log('3:', [10, 30, 50].map(y => px(x3, 100, y)).join(' | '));
  const x4 = document.createElement('canvas').getContext('2d');
  x4.translate(100, 0);
  x4.beginPath();  x4.rect(0, 0, 50, 50);
  const p4 = new Path2D();  p4.rect(0, 0, 50, 50);
  x4.resetTransform();
  console.log('4:', x4.isPointInPath(125, 25), x4.isPointInPath(p4, 125, 25));
  const x5 = c5.getContext('2d');
  x5.scale(2, 2);  x5.shadowColor = 'black';  x5.shadowOffsetX = 10;
  x5.fillStyle = 'orange';  x5.fillRect(10, 5, 20, 20);     // covers pixels 20 to 59
  const row = x5.getImageData(0, 30, 200, 1).data, dark = [];
  for (let x = 0; x < 200; x++) if (row[x * 4 + 3] && !row[x * 4]) dark.push(x);
  console.log('5:', dark[0], 'to', dark.at(-1));
</script>
questions-6-10.html: settings, early images, alpha, hints and taintingHTMLLive
<!-- 6. A font without a family, and a letter spacing without a unit. What do the two
        properties read back? (Sections 2.4.1 and 2.4.3) -->
<!-- 7. drawImage() straight after setting src. Is anything drawn? Is anything thrown?
        (Section 2.5.1) -->
<!-- 8. One pixel written with putImageData() and read back. Is it unchanged?
        (Section 2.5.2) -->
<!-- 9. willReadFrequently passed on the second getContext() call. Does it apply?
        (Section 2.5.2) -->
<!-- 10. The image has loaded, is drawn, and its pixels are read. What happens when
         the page is opened from disk? (Section 2.5) -->
<script>
  const ctx = document.createElement('canvas').getContext('2d');
  ctx.font = '20px';  ctx.letterSpacing = '4';
  console.log('6:', ctx.font, '|', ctx.letterSpacing);
  const img = new Image();  img.src = 'booknest-logo.png';
  try {
    ctx.drawImage(img, 0, 0);  console.log('7: alpha', ctx.getImageData(5, 5, 1, 1).data[3]);
  } catch (e) { console.log('7:', e.name); }
  ctx.putImageData(new ImageData(new Uint8ClampedArray([100, 150, 200, 3]), 1, 1), 0, 0);
  console.log('8:', ctx.getImageData(0, 0, 1, 1).data.join(','));
  const c9 = document.createElement('canvas');  c9.getContext('2d');
  const again = c9.getContext('2d', { willReadFrequently: true });
  console.log('9:', again.getContextAttributes().willReadFrequently);
  img.onload = () => {
    const x10 = document.createElement('canvas').getContext('2d');  x10.drawImage(img, 0, 0);
    try { console.log('10:', x10.getImageData(0, 0, 1, 1).data.join(',')); }
    catch (e) { console.log('10:', e.name); }
  };
</script>