그래픽 처리 개요

Dart에서 2D 그래픽을 처리하기 위해서는 다양한 라이브러리와 도구를 활용할 수 있다. 특히 Flutter는 Dart에서 2D 그래픽을 다루는 데 매우 유용한 도구를 제공한다. Flutter에서는 CustomPainter 클래스를 활용하여 화면에 2D 그래픽을 그릴 수 있다. 이 클래스는 화면을 직접 제어할 수 있는 강력한 기능을 제공하며, 다양한 그래픽 요소를 캔버스에 그릴 수 있도록 지원한다.

CustomPainter 개념

CustomPainter는 Flutter에서 제공하는 클래스로, 사용자가 원하는 그래픽 요소를 그릴 수 있는 도구이다. 이 클래스를 통해 기본적인 도형부터 복잡한 패턴에 이르기까지 다양한 2D 그래픽을 구현할 수 있다. 다음은 CustomPainter를 사용하는 방법에 대한 기본 흐름이다.

class MyPainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    var paint = Paint()
      ..color = Colors.blue
      ..strokeWidth = 5;

    // 직선을 그린다
    canvas.drawLine(Offset(0, 0), Offset(size.width, size.height), paint);
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    return false;
  }
}

위의 예제는 간단한 직선을 그리는 코드이다. Canvas 객체는 화면에 그리는 역할을 하고, Paint 객체는 그릴 도형의 스타일을 정의한다.

2D 좌표계

Dart에서의 2D 그래픽 처리는 기본적으로 좌표계를 바탕으로 한다. Canvas에서의 좌표계는 왼쪽 상단을 기준으로 하여 오른쪽과 아래쪽으로 증가하는 방식이다. 이 좌표계는 수학에서의 좌표계와 다르며, 원점을 기준으로 X축은 오른쪽, Y축은 아래쪽으로 향한다.

이를 수학적으로 표현하면 다음과 같다.

\mathbf{x}_{canvas} = \begin{pmatrix} x_{1} \\ y_{1} \end{pmatrix}, \quad \mathbf{x}_{math} = \begin{pmatrix} x_{1} \\ -y_{1} \end{pmatrix}

직선 그리기

직선을 그리는 것은 2D 그래픽에서 가장 기본적인 작업 중 하나이다. Dart에서는 Canvas 객체를 사용하여 직선을 그릴 수 있다. 직선은 두 점 사이를 연결하는 것으로, 두 점을 (x_1, y_1)(x_2, y_2)로 정의할 수 있다. 수학적으로 직선 방정식은 다음과 같이 정의할 수 있다.

y - y_1 = \frac{y_2 - y_1}{x_2 - x_1} (x - x_1)

이 방정식을 통해 두 점 사이의 직선을 그릴 수 있다. Dart에서는 이를 다음과 같이 구현할 수 있다.

canvas.drawLine(Offset(x1, y1), Offset(x2, y2), paint);

여기서 Offset은 좌표를 나타내는 객체이고, paint는 그릴 스타일을 지정한다.

원 그리기

Dart에서는 drawCircle 메소드를 사용하여 원을 그릴 수 있다. 원은 중심점과 반지름으로 정의되며, 수학적으로는 다음과 같이 정의된다.

(x - h)^2 + (y - k)^2 = r^2

여기서 (h, k)는 원의 중심 좌표이고, r은 반지름이다. 이 공식을 바탕으로 Dart에서 원을 그리는 방법은 다음과 같다.

canvas.drawCircle(Offset(h, k), r, paint);

다각형 그리기

다각형을 그리는 방법은 여러 개의 직선을 연결하여 닫힌 도형을 만드는 방식이다. Dart에서는 drawPath 메소드를 사용하여 여러 점을 연결한 도형을 그릴 수 있다. 예를 들어, 삼각형을 그리기 위해서는 세 개의 점을 지정하고 이들을 Path 객체로 연결하면 된다.

수학적으로 삼각형은 세 꼭짓점 (x_1, y_1), (x_2, y_2), (x_3, y_3)로 정의되며, Dart에서 이를 그리는 방법은 다음과 같다.

var path = Path();
path.moveTo(x1, y1);
path.lineTo(x2, y2);
path.lineTo(x3, y3);
path.close();

canvas.drawPath(path, paint);

이 코드는 세 점을 연결하여 삼각형을 그린 후, 경로를 닫는 close 메소드를 호출하여 다각형을 완성한다.

곡선 그리기

Dart에서는 Canvas 객체를 통해 다양한 형태의 곡선을 그릴 수 있다. 곡선은 기본적으로 베지어 곡선을 사용하여 구현된다. Dart에서 제공하는 drawPath 메소드를 사용하면 곡선을 더욱 정밀하게 그릴 수 있다.

2차 베지어 곡선

2차 베지어 곡선은 시작점, 제어점, 끝점으로 정의된다. 수학적으로, 2차 베지어 곡선은 다음과 같은 식으로 표현된다.

\mathbf{P}(t) = (1 - t)^2 \mathbf{P}_0 + 2(1 - t)t \mathbf{P}_1 + t^2 \mathbf{P}_2, \quad 0 \leq t \leq 1

여기서 \mathbf{P}_0는 시작점, \mathbf{P}_1은 제어점, \mathbf{P}_2는 끝점이다. Dart에서 2차 베지어 곡선을 그리기 위해 quadraticBezierTo 메소드를 사용할 수 있다.

var path = Path();
path.moveTo(startX, startY);
path.quadraticBezierTo(controlX, controlY, endX, endY);

canvas.drawPath(path, paint);

3차 베지어 곡선

3차 베지어 곡선은 시작점, 두 개의 제어점, 끝점으로 정의된다. 수학적으로는 다음과 같은 식으로 표현된다.

\mathbf{P}(t) = (1 - t)^3 \mathbf{P}_0 + 3(1 - t)^2 t \mathbf{P}_1 + 3(1 - t) t^2 \mathbf{P}_2 + t^3 \mathbf{P}_3, \quad 0 \leq t \leq 1

여기서 \mathbf{P}_0는 시작점, \mathbf{P}_1\mathbf{P}_2는 제어점, \mathbf{P}_3는 끝점이다. 3차 베지어 곡선을 Dart에서 구현할 때는 cubicTo 메소드를 사용한다.

var path = Path();
path.moveTo(startX, startY);
path.cubicTo(controlX1, controlY1, controlX2, controlY2, endX, endY);

canvas.drawPath(path, paint);

그라디언트 및 채우기

Dart에서는 2D 그래픽을 그릴 때 색상뿐만 아니라 그라디언트(Gradient)도 활용할 수 있다. Paint 객체를 설정하여 색상을 그라디언트로 지정하면 점진적인 색상 변화를 표현할 수 있다.

선형 그라디언트

선형 그라디언트는 두 점 사이의 색상 변화를 표현한다. 수학적으로는 두 점 (x_1, y_1)에서 (x_2, y_2)까지의 선을 따라 색상이 변하는 방식이다. Dart에서는 LinearGradient 클래스를 사용하여 선형 그라디언트를 설정할 수 있다.

var paint = Paint()
  ..shader = LinearGradient(
    colors: [Colors.red, Colors.blue],
    begin: Alignment.topLeft,
    end: Alignment.bottomRight,
  ).createShader(Rect.fromLTWH(0, 0, 200, 200));

canvas.drawRect(Rect.fromLTWH(0, 0, 200, 200), paint);

방사형 그라디언트

방사형 그라디언트는 중심점에서 바깥쪽으로 색상이 변하는 방식이다. 수학적으로 방사형 그라디언트는 중심점 (x_c, y_c)에서 반지름 r까지의 범위 내에서 색상이 변화하는 패턴을 나타낸다. Dart에서 이를 구현할 때는 RadialGradient 클래스를 사용한다.

var paint = Paint()
  ..shader = RadialGradient(
    colors: [Colors.yellow, Colors.green],
  ).createShader(Rect.fromCircle(center: Offset(100, 100), radius: 100));

canvas.drawCircle(Offset(100, 100), 100, paint);

클리핑 (Clipping)

클리핑은 그래픽을 그릴 때 특정 영역 외부의 부분을 자르는 작업을 의미한다. 이를 통해 그래픽의 일부만 보이게 할 수 있으며, Dart에서는 clipRect, clipPath 등을 사용하여 클리핑 작업을 할 수 있다. 예를 들어, 사각형 영역을 클리핑하여 그 안에서만 그래픽을 그리도록 할 수 있다.

canvas.clipRect(Rect.fromLTWH(50, 50, 100, 100));
canvas.drawCircle(Offset(100, 100), 50, paint);

이 코드는 사각형 영역 내에서만 원이 그려지도록 제한한다.

변환 (Transform)

Dart에서는 2D 그래픽 요소를 변환할 수 있는 기능을 제공한다. 변환에는 이동, 회전, 스케일링 등의 작업이 포함된다. 변환 행렬을 사용하여 그래픽 요소의 좌표계를 조정할 수 있다.

이동 변환

이동 변환은 그래픽 요소를 특정 방향으로 이동시키는 작업이다. 수학적으로 이동 변환은 다음과 같은 행렬로 표현된다.

\mathbf{T} = \begin{pmatrix} 1 & 0 & t_x \\ 0 & 1 & t_y \\ 0 & 0 & 1 \end{pmatrix}

여기서 t_xt_y는 각각 x축과 y축 방향으로의 이동 거리를 나타낸다. Dart에서는 translate 메소드를 사용하여 이동 변환을 적용할 수 있다.

canvas.translate(50, 50);
canvas.drawRect(Rect.fromLTWH(0, 0, 100, 100), paint);

회전 변환

회전 변환은 그래픽 요소를 중심점 기준으로 회전시키는 작업이다. 수학적으로 회전 변환은 다음과 같은 행렬로 표현된다.

\mathbf{R} = \begin{pmatrix} \cos \theta & -\sin \theta & 0 \\ \sin \theta & \cos \theta & 0 \\ 0 & 0 & 1 \end{pmatrix}

여기서 \theta는 회전 각도이다. Dart에서는 rotate 메소드를 사용하여 회전 변환을 적용할 수 있다.

canvas.rotate(math.pi / 4);
canvas.drawRect(Rect.fromLTWH(0, 0, 100, 100), paint);

스케일 변환

스케일 변환은 그래픽 요소의 크기를 조정하는 작업이다. 수학적으로 스케일 변환은 다음과 같은 행렬로 표현된다.

\mathbf{S} = \begin{pmatrix} s_x & 0 & 0 \\ 0 & s_y & 0 \\ 0 & 0 & 1 \end{pmatrix}

여기서 s_xs_y는 각각 x축과 y축 방향으로의 스케일 팩터이다. Dart에서는 scale 메소드를 사용하여 스케일 변환을 적용할 수 있다.

canvas.scale(2, 2);
canvas.drawRect(Rect.fromLTWH(0, 0, 50, 50), paint);

이 코드는 사각형을 두 배로 확대하여 그린다.