Drawing and Cropping Images

drawImage(img, dx, dy) draws at natural size, (img, dx, dy, dw, dh) scales, and (img, sx, sy, sw, sh, dx, dy, dw, dh) crops a source rectangle into a target one, as sprite sheets need. Wait for img.decode() first. Scaling is smoothed unless you set imageSmoothingEnabled = false.

The logo scaled, its mark cropped out, and a patch enlarged without smoothingHTML
<canvas id="c" style="width:600px; height:96px"></canvas><script src="hidpi.js"></script>
<script>
  const ctx = hidpi(c), img = Object.assign(new Image(), { src: 'booknest-logo.png' });
  img.decode().then(() => {
    ctx.drawImage(img, 0, 0, 300, 95);                     // whole image at half size
    ctx.drawImage(img, 0, 0, 190, 190, 320, 0, 95, 95);    // crop: source rect, target rect
    ctx.imageSmoothingEnabled = false;
    ctx.drawImage(img, 90, 70, 24, 24, 440, 0, 96, 96);    // a 24-pixel patch, four times
    window.__done = true;
  });
</script>
Browser output of Listing 2.16
Browser output of 16

The logo is a 600 x 190 PNG of Transforms and the Logo's SVG; the patch shows its antialiased edges as hard squares.

drawImage's three forms: natural size, scaled, and cropped from a source rectangleHTMLLive
<!doctype html>
<style>
  body { margin: 0; padding: 8px; font: 13px system-ui, sans-serif; background: #fafaf7; }
  canvas { width: 100%; max-width: 600px; height: 150px; display: block; background: #fff; border: 1px solid #ddd; }
</style>
<canvas id="c"></canvas>
<label><input type="checkbox" id="smooth"> imageSmoothingEnabled for the zoomed patch</label>
<script>
  function hidpi(c, r = devicePixelRatio) {
    c.width = c.clientWidth * r;  c.height = c.clientHeight * r;
    const ctx = c.getContext('2d');  ctx.scale(r, r);  return ctx;
  }
  const ctx = hidpi(document.getElementById('c'));

  // A 600×190 BookNest logo, generated as an SVG data URL instead of fetching a PNG
  const logo = `<svg xmlns="http://www.w3.org/2000/svg" width="600" height="190">
    <rect width="600" height="190" fill="#fff3e0"/>
    <g transform="translate(95 170)">
      <rect x="-60" y="-150" width="26" height="110" fill="#1f5f8b" transform="rotate(-12)"/>
      <rect x="-28" y="-160" width="26" height="120" fill="#e09a10"/>
      <rect x="4" y="-140" width="26" height="100" fill="#3f7d3a" transform="rotate(10)"/>
      <path d="M-80 -45 Q0 -20 80 -45 Q70 10 0 12 Q-70 10 -80 -45Z" fill="#263238"/>
    </g>
    <text x="200" y="118" font-family="Georgia" font-weight="bold" font-size="72" fill="#1f5f8b">BookNest</text>
  </svg>`;
  const img = new Image();
  img.src = 'data:image/svg+xml,' + encodeURIComponent(logo);

  function draw(smooth) {
    ctx.clearRect(0, 0, 600, 150);
    ctx.imageSmoothingEnabled = true;
    ctx.drawImage(img, 0, 0, 300, 95);                   // (img, dx, dy, dw, dh): scaled to half
    ctx.drawImage(img, 0, 0, 190, 190, 320, 0, 95, 95);  // (img, sx, sy, sw, sh, dx, dy, dw, dh): crop
    ctx.imageSmoothingEnabled = smooth;
    ctx.drawImage(img, 196, 96, 24, 24, 440, 0, 144, 144); // a 24-pixel patch of the B, six times larger
    ctx.font = '11px sans-serif';  ctx.fillStyle = '#263238';
    ctx.fillText('scaled 600×190 → 300×95', 10, 115);
    ctx.fillText('cropped 190×190 → 95×95', 300, 115);
    ctx.strokeStyle = '#b5452f';  ctx.strokeRect(440, 0, 144, 144);
  }
  // Wait for decode() before drawing, or nothing appears
  img.decode().then(() => draw(false));
  document.getElementById('smooth').addEventListener('change', e => draw(e.target.checked));
</script>