DOM/Canvas

1-6. 변형 - Canvas API | MDN

AGAL 2021. 3. 15. 17:16
반응형

 

이 튜토리얼의 앞부분에서는 canvas 그리드와 좌표 공간에 대해 알아 보았습니다. 지금까지는 기본적인 그리드만 사용하고 필요에 따라 전체 canvas의 크기를 바꾸기만 했습니다. Transformation(변형)에는 그리드를 원점에서 다른 위치로 옮기고, 회전하며, 확대·축소까지 하는 더 강력한 방법들이 있습니다.

 

 

상태(state)의 저장과 복원

변형(transformation) 메소드를 살펴보기 전에, 더 복잡한 도면을 생성하기 시작하면 반드시 필요한 두 가지 다른 방법을 살펴보겠습니다.

  • save() : canvas의 모든 상태를 저장합니다.
  • restore() : 가장 최근에 저장된 canvas 상태를 복원합니다.

 

Canvas 상태는 스택(stack)에 쌓입니다. save() 메소드가 호출될 때마다 현재 drawing 상태가 스택에 푸시됩니다. drawing 상태는 다음과 같이 이루어집니다.

  • 이전부터 적용된 변형(가령,  translate과 rotate와 scale 같은 – 아래 참조).
  • 다음 속성(attributes)의 현재 값: strokeStyle, fillStyle, globalAlpha, lineWidth, lineCap, lineJoin, miterLimit, lineDashOffset, shadowOffsetX, shadowOffsetY, shadowBlur, shadowColor, globalCompositeOperation, font, textAlign, textBaseline, direction, imageSmoothingEnabled.
  • 현재의 clipping path(클리핑 경로), 다음 섹션에서 확인할 수 있습니다.

 

save() 메소드는 원하는 횟수만큼 호출할 수 있습니다. restore() 메소드를 호출할 때마다 마지막으로 저장된 상태가 스택에서 튀어나와 저장된 설정들을 모두 복원시킵니다.

 

save와 restore 캔버스 상태(Canvas state) 예제

이 예에서는 연속된 직사각형 집합을 그려 도면 상태 스택이 어떻게 기능하는지 설명하려고 합니다.

function draw() {
  var ctx = document.getElementById('canvas').getContext('2d');

  ctx.fillRect(0, 0, 150, 150);   // 기본 설정으로 사각형을 그립니다.
  ctx.save();                  // 기본 상태를 저장합니다.

  ctx.fillStyle = '#09F';      // 설정을 변경합니다.
  ctx.fillRect(15, 15, 120, 120); // 새 설정으로 사각형을 그립니다.

  ctx.save();                  // 현재 상태를 저장합니다.
  ctx.fillStyle = '#FFF';      // 설정을 변경합니다.
  ctx.globalAlpha = 0.5;
  ctx.fillRect(30, 30, 90, 90);   // 새 설정으로 사각형을 그립니다.

  ctx.restore();               // 이전 상태를 복원합니다.
  ctx.fillRect(45, 45, 60, 60);   // 복원된 설정으로 사각형을 그립니다.

  ctx.restore();               // 원래 상태를 복원합니다.
  ctx.fillRect(60, 60, 30, 30);   // 복원된 설정으로 사각형을 그립니다.
}

첫 번째 단계는 기본 설정으로 커다란 사각형을 그립니다. 그 다음 이 상태를 저장하고 fill color를 바꿉니다. 그 후에 두 번째이자 크기가 더 작은 파란 사각형을 그리고 상태를 저장합니다. 다시 한번 일부 drawing 설정을 바꾸고 세 번째 반투명 흰색 사각형을 그립니다. 

 

여기까지는 이전 섹션에서 했던 작업과 매우 유사합니다. 하지만 일단 첫 번째 restore() 문을 호출하면 스택에서 맨 위의 drawing 상태가 지워지고 설정이 복원됩니다. 만일 save() 메소드로 저장하지 않았다면, 이전 상태로 되돌리기 위해 fill color와 투명도를 일일이 바꿔주어야 했을 것입니다. 두 속성이라서 간단했을 테지만 그보다 더 많았으면 코드가 급속히 길어졌겠지요. 

 

두 번째 restore() 문이 호출될 때, 초기 상태(처음 save() 를 호출하기 전에 설정한 상태)가 복원되고 마지막 사각형이 다시 검은색으로 그려집니다.

 

 

이동(Translating)

우리가 살펴볼 첫 번째 변형 메소드는 translate()입니다.  이 메소드는 그리드에서 canvas를 원점에서 다른 점으로 옮기는 데 사용됩니다. 

  • translate(x, y) : 그리드에서 canvas와 그 원점을 이동합니다. x는 이동시킬 수평 거리를 가리키고, y는 그리드에서 수직으로 얼마나 멀리 떨어지는지를 표시합니다. 

 

변형하기 전에 canvas 상태를 저장하는 것이 좋습니다. 대다수의 경우, 원래 상태로 되돌리려고 역이동(reverse translation)을 시키는 것보다 restore() 메소드를 호출하는 것이 더 쉽습니다. 또한 루프 내부에서 이동하는 경우 canvas 상태를 저장 및 복원하지 않으면 canvas 가장자리 밖에서 그려져 있기 때문에 drawing의 일부가 누락될 수 있습니다.

 

Translate 예제

이 예제에서는 canvas 원점을 이동할 때 몇 가지 이점을 보여 줍니다. translate() 메소드를 쓰지 않으면 모든 사각형은 같은 위치 (0, 0)에 그려집니다. 이렇게 하면 좀 더 쉽게 이해하고 사용할 수 있습니다.

 

draw() 함수에서 두 개의 루프(loops)를 이용해 fillRect() 메소드를 사용하여 drawing 위치를 조정하는 지 눈여겨 보세요.

function draw() {
  var ctx = document.getElementById('canvas').getContext('2d');
  for (var i = 0; i < 3; i++) {
    for (var j = 0; j < 3; j++) {
      ctx.save();
      ctx.fillStyle = 'rgb(' + (51 * i) + ', ' + (255 - 51 * i) + ', 255)';
      ctx.translate(10 + j * 50, 10 + i * 50);
      ctx.fillRect(0, 0, 25, 25);
      ctx.restore();
    }
  }
}

 

 

회전(rotating)

두 번째 변형 메소드는 rotate()입니다. canvas를 현재의 원점을 중심으로 회전하는 데 사용합니다.

  • rotate(angle) : canvas를 현재 원점을 기준으로 라디안의 각도 숫자만큼 시계방향으로 회전합니다.

 

회전의 중심점은 언제나 canvas 원점입니다. 중심점을 바꾸려면 translate() 메소드를 써서 canvas를 이동해야 합니다.

 

rotate 예제

메소드를 사용하여 직사각형의 중앙에서 회전합니다.

 

주의: 각도의 단위는 도(degree)가 아닌 라디안(radian)입니다.  변환하려면 radians = (Math.PI/180)*degrees.를 사용합니다.

 

function draw() {
  var ctx = document.getElementById('canvas').getContext('2d');

  // 왼쪽 직사각형, 캔버스 원점에서 회전
  ctx.save();
  // 파란색 사각형
  ctx.fillStyle = '#0095DD';
  ctx.fillRect(30, 30, 100, 100);
  ctx.rotate((Math.PI / 180) * 25);
  // 회색 사각형
  ctx.fillStyle = '#4D4E53';
  ctx.fillRect(30, 30, 100, 100);
  ctx.restore();

  // 오른쪽 사각형, 사각형 중앙에서 회전
  // 파란색 사각형 그리기
  ctx.fillStyle = '#0095DD';
  ctx.fillRect(150, 30, 100, 100);

  ctx.translate(200, 80); // 직사각형 중심으로 변환
                          // x = x + 0.5 * width
                          // y = y + 0.5 * height
  ctx.rotate((Math.PI / 180) * 25); // 회전
  ctx.translate(-200, -80); // 역변환

  // 회색 사각형 그리기
  ctx.fillStyle = '#4D4E53';
  ctx.fillRect(150, 30, 100, 100);
}

 

직사각형을 자신의 중심으로 회전시키기 위해 캔버스를 직사각형의 중심으로 변환합니다. 그 다음 캔버스를 회전하고, 캔버스를 0, 0으로 변환합니다. 그 다음 직사각형을 그립니다.

 

 

확대·축소(scaling)

다음 변형 메소드는 확대·축소(scaling)입니다. canvas 그리드에서 단위(units)를 늘리거나 줄이는 데 사용합니다. 이는 벡터 모양과 비트맵(bitmaps) 요소를 축소하거나 확대해서 그리는 데 사용될 수 있습니다.

  • scale(x, y) : canvas 단위를 수평으로 x만큼, 수직으로 y만큼 크기를 확대·축소합니다. 둘의 매개 변수는 실수입니다. 1.0보다 작은 값이면 단위의 크기를 축소하고, 1.0보다 큰 값이면 단위의 크기를 확대합니다. 1.0의 값은 단위의 크기를 그대로 유지합니다.

음수를 이용해서 축을 대칭 시킬 수 있습니다. (예: translate(0, canvas.height); scale(1,-1); 잘 알려진 데카르트 좌표계가 있으며 원점은 왼쪽 아래 모서리에 있습니다.)

기본적으로 canvas에서 하나의 단위는 정확히 1픽셀입니다. 예를 들어 0.5라는 확대·축소 비율을 적용한다면, 결과로 나오는 단위는 0.5 픽셀이 될 것이고, 고로 모양도 절반 크기로 그려질 것입니다. 이런 방식으로 크기 비율을 2.0으로 잡으면 단위 크기가 확대되어 하나의 단위는 이제 2픽셀이 되겠지요. 이 결과로 모양은 그만큼 2배로 커집니다.

 

scale 예제

이 마지막 예제에서는 서로 다른 배율 인자를 사용하여 도형을 그립니다.

function draw() {
  var ctx = document.getElementById('canvas').getContext('2d');

  // 간단한 직사각형을 그리지만, 크기를 조정하세요.
  ctx.save();
  ctx.scale(10, 3);
  ctx.fillRect(1, 10, 10, 10);
  ctx.restore();

  // 수평으로 반전
  ctx.scale(-1, 1);
  ctx.font = '48px serif';
  ctx.fillText('MDN', -135, 120);
}

 

 

변형(transforms)

마지막으로, 다음의 변형(transform) 메소드를 사용하면 변환 행렬(transformation matrix)을 직접 수정할 수 있습니다.

  • transform(a, b, c, d, e, f) : 현재 변환 행렬에 인수로 설명된 행렬을 곱합니다.
    변환 행렬은 다음과 같이 설명됩니다. \left[ \begin{array}{ccc} a & c & e \\ b & d & f \\ 0 & 0 & 1 \end{array} \right]
    인수 중 하나가 Infinity인 경우, 변환 행렬은 예외 처리하는 메소드 대신 infinite로 표시되어야 합니다.
    function의 매개 변수들은 다음과 같습니다.
    - a (m11) : 수평으로 확대·축소하기
    - b (m12) : 수평으로 비스듬히 기울이기
    - c (m21) : 수직으로 비스듬히 기울이기
    - d (m22) : 수직으로 확대·축소하기
    - e (dx) : 수평으로 이동하기
    - f (dy) : 수직으로 이동하기
  • setTransform(a, b, c, d, e, f) :
    현재 변형 상태를 단위 행렬로 재설정하고 나서 동일한 인수로 transform() 메소드를 호출합니다. 이는 기본적으로 현재의 변형을 무효로 한 후에 지정된 변형을 모두 한한번에 모든 게 진행됩니다.
  • resetTransform() :
    현재 변형 상태를 단위 행렬로 재설정합니다. 이는 ctx.setTransform(1, 0, 0, 1, 0, 0); 호출과 같습니다.

 

transform과 setTransform 예제

function draw() {
  var ctx = document.getElementById('canvas').getContext('2d');

  var sin = Math.sin(Math.PI / 6);
  var cos = Math.cos(Math.PI / 6);
  ctx.translate(100, 100);
  var c = 0;
  for (var i = 0; i <= 12; i++) {
    c = Math.floor(255 / 12 * i);
    ctx.fillStyle = 'rgb(' + c + ', ' + c + ', ' + c + ')';
    ctx.fillRect(0, 0, 100, 10);
    ctx.transform(cos, sin, -sin, cos, 0, 0);
  }

  ctx.setTransform(-1, 0, 0, 1, 100, 100);
  ctx.fillStyle = 'rgba(255, 128, 255, 0.5)';
  ctx.fillRect(0, 50, 100, 100);
}

 

반응형