Translate, Rotate, Scale

Translating, Rotating and Scaling

translate(x, y) moves the origin, rotate(angle) turns about it (radians, clockwise) and scale(sx, sy) stretches it by any factor, mirroring when negative. Each call multiplies the current matrix. transform() multiplies by any matrix (transform(1, 0, -0.4, 1, 0, 0) skews), setTransform() replaces it and getTransform() returns it as a DOMMatrix.

A fan of covers turned about one pivot, and a mirrored reflectionHTML
<canvas id="c" style="width:600px; height:120px"></canvas><script src="hidpi.js"></script>
<script>
  const ctx = hidpi(c);
  ['#1f5f8b', '#5b3f99', '#e09a10', '#3f7d3a', '#b5452f', '#2a9d8f'].forEach((color, i) => {
    ctx.save();  ctx.translate(100, 160);  ctx.rotate((i - 2.5) * 0.2);  // pivot below
    ctx.fillStyle = color;  ctx.fillRect(-14, -155, 28, 80);  ctx.restore();
  });
  ctx.font = 'bold 34px Georgia';  ctx.fillStyle = '#1f5f8b';
  ctx.fillText('BookNest', 220, 60);
  ctx.save();  ctx.translate(0, 128);  ctx.scale(1, -1);                // mirror about y = 64
  ctx.globalAlpha = 0.25;  ctx.fillText('BookNest', 220, 60);  ctx.restore();
</script>
Browser output of Listing 2.20
Browser output of 20

To rotate about a point, translate to it, rotate and translate back.

translate, rotate and scale: fanned spines, a mirrored reflection and a skewed shadowHTMLLive
<!doctype html>
<style>
  body { margin: 0; padding: 8px; font: 13px system-ui, sans-serif; background: #fafaf7; }
  canvas { width: 100%; max-width: 600px; height: 170px; display: block; background: #fff; border: 1px solid #ddd; }
</style>
<canvas id="c"></canvas>
<label>Fan angle <input type="range" id="fan" min="0" max="40" value="16"></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'));
  const colors = ['#1f5f8b', '#5b3f99', '#e09a10', '#3f7d3a', '#b5452f', '#2a9d8f'];
  const base = ctx.getTransform();                 // the hidpi scale, as a DOMMatrix

  function draw(fanDegrees) {
    ctx.setTransform(base);                        // replace, don't multiply
    ctx.clearRect(0, 0, 600, 170);
    // Rotate each spine about a pivot below the books: translate there, then rotate
    colors.forEach((color, i) => {
      ctx.save();
      ctx.translate(120, 200);
      ctx.rotate((i - 2.5) * fanDegrees * Math.PI / 180);
      ctx.fillStyle = color;  ctx.fillRect(-14, -185, 28, 90);
      ctx.restore();
    });

    ctx.font = 'bold 40px Georgia';  ctx.fillStyle = '#1f5f8b';
    ctx.fillText('BookNest', 230, 80);
    // scale(1, -1) mirrors about the origin, so translate to y = 2 × 84 first
    ctx.save();  ctx.translate(0, 168);  ctx.scale(1, -1);
    ctx.globalAlpha = 0.2;  ctx.fillText('BookNest', 230, 80);  ctx.restore();

    // transform(a, b, c, d, e, f) multiplies by any matrix: here a horizontal skew
    ctx.save();  ctx.transform(1, 0, -0.6, 1, 0, 0);
    ctx.fillStyle = 'rgb(0 0 0 / 0.15)';  ctx.fillRect(560, 40, 26, 90);  ctx.restore();
    ctx.fillStyle = '#b5452f';  ctx.fillRect(500, 40, 26, 90);

    const m = ctx.getTransform();
    ctx.font = '11px monospace';  ctx.fillStyle = '#263238';
    ctx.fillText(`getTransform(): a=${m.a.toFixed(2)} d=${m.d.toFixed(2)}`, 230, 160);
  }
  const slider = document.getElementById('fan');
  slider.addEventListener('input', () => draw(+slider.value));
  draw(16);
</script>