상세 컨텐츠

본문 제목

C# Graphics 클래스를 사용하여 그리기 (심화)

프로그래밍/C#

by TickTack 2021. 3. 11. 13:14

본문

이번에는 Graphics 클래스를 이용해서 그림을 그리기 위해 사용하는 조금 더 고급스러운 방법을 사용해보겠습니다.

먼저 빈 프로젝트를 만든 후 폼의 Paint 이벤트를 생성 후 그 안에 아래와 같이 코드를 작성합니다.

 

private void Form1_Paint(object sender, PaintEventArgs e)
{
    Graphics graphic = e.Graphics;
    HardDraw(graphic);
}

 

그 다음 HardDraw 함수는 아래의 코드를 이용하여 작성합니다.

 

private void HardDraw(Graphics graphic)
{
    GraphicsPath path = new GraphicsPath();
    Pen pen = new Pen(Color.Red, 2);
    graphic.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;

    Matrix matrix = new Matrix();
    Brush brush = new SolidBrush(Color.FromArgb(40, 80, 120));

    path.AddLine(115, 115, 110, 125);
    path.AddLine(110, 125, 100, 125);
    path.AddLine(100, 125, 110, 130);
    path.AddLine(110, 130, 105, 140);
    path.AddLine(105, 140, 115, 132);
    path.AddLine(115, 132, 125, 140);
    path.AddLine(125, 140, 120, 130);
    path.AddLine(120, 130, 130, 125);
    path.AddLine(130, 125, 120, 125);
    path.AddLine(120, 125, 115, 115);

    graphic.DrawPath(pen, path);
    graphic.FillPath(brush, path);
    graphic.DrawString("원본", new Font("맑은 고딕", 12), Brushes.Black, new PointF(95, 150));

    // 회전 과정 그리기
    for (int i = 0; i < 8; i++)
    {
        matrix.Translate(100 + (i * 75), 0);
        matrix.RotateAt(45 * (i + 1), path.PathPoints[0]);  // 회전 시 offset 값도 같이 비례해서 바뀜
        graphic.Transform = matrix;  // 회전 적용 후 그린다.

        graphic.DrawPath(pen, path);
        graphic.FillPath(Brushes.Yellow, path);

        matrix.Reset();
        graphic.Transform = matrix;
        graphic.DrawString((45 * (i + 1)) + "˚", new Font("맑은 고딕", 12), Brushes.Black, new PointF(190 + (i * 75), 150));
    }

    // 사각형 1
    Rectangle rec = new Rectangle(50, 250, 150, 150);
    Rectangle clipRec = new Rectangle(100, 250, 50, 50);
    Rectangle clipRec2 = new Rectangle(50, 300, 150, 50);
    Rectangle clipRec3 = new Rectangle(50, 350, 25, 25);
    Rectangle clipRec4 = new Rectangle(175, 350, 25, 25);

    graphic.ExcludeClip(clipRec);
    graphic.ExcludeClip(clipRec2);
    graphic.ExcludeClip(clipRec3);
    graphic.ExcludeClip(clipRec4);

    graphic.DrawRectangle(pen, rec);
    graphic.FillRectangle(Brushes.Pink, rec);

    // 사각형 2
    rec = new Rectangle(250, 250, 150, 150);
    clipRec = new Rectangle(250, 250, 50, 50);

    graphic.ResetClip();
    graphic.IntersectClip(clipRec);

    graphic.DrawRectangle(pen, rec);
    graphic.FillRectangle(Brushes.LightGreen, rec);

    // 사각형 3
    rec = new Rectangle(450, 250, 150, 150);
    clipRec = new Rectangle(500, 250, 50, 50);
    clipRec2 = new Rectangle(450, 300, 150, 50);
    clipRec3 = new Rectangle(450, 350, 25, 25);
    clipRec4 = new Rectangle(575, 350, 25, 25);
    Region region = new Region(rec);

    graphic.ResetClip();
    region.Exclude(rec);

    region.Xor(clipRec);
    region.Xor(clipRec2);
    region.Xor(clipRec3);
    region.Xor(clipRec4);

    graphic.DrawRectangle(pen, rec);
    graphic.FillRegion(Brushes.LightGreen, region);
}

 

아래는 코드의 결과입니다.

 

 

아래는 코드에 대한 설명입니다.

 

private void Form1_Paint(object sender, PaintEventArgs e)
{
      Graphics graphic = e.Graphics;
      HardDraw(graphic);
}

 

제일 먼저 Paint 이벤트에서 HardDraw 함수에 e.Graphics로 생성한 graphic 변수를 인자로 넘겨줍니다.

넘겨준 graphic 변수를 이용하여 그림을 그릴 것 입니다. 캔버스라고 생각하면 쉽습니다.

 

HardDraw 함수의 { }는 원활한 설명을 위하여 원래 코드대로 삽입되어 있으니 참고해주세요.

 

private void HardDraw(Graphics graphic)
{
      GraphicsPath path = new GraphicsPath();
      Pen pen = new Pen(Color.Red, 2);
      graphic.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;

 

다음은 HardDraw 함수안으로 진입해서 GraphicsPath 클래스로 변수를 1개 생성합니다.

Pen 클래스의 변수를 생성합니다. 색상은 Red, 굵기는 2입니다.

그림을 그릴 graphic 변수에 SmoothingModeAntiAlias로 설정합니다.

이걸로 설정하는 이유는 그림의 품질을 좀 더 올리기위해 사용합니다.

 

* AntiAlias : 픽셀로 그림을 그릴 때 대각선, 곡선 등을 그리면

                계단식 현상이 발생하여 전체적으로 보았을 때 부드럽지 않고 거칠게 표현되는데

                이 때 선의 주변에 비슷한 색상을 같이 그려서 전체적으로 부드럽게 보이게 하는 기술

 

Matrix matrix = new Matrix();
Brush brush = new SolidBrush(Color.FromArgb(40, 80, 120));

 

Matrix 클래스로 변수를 1개 만들어줍니다. 이걸 이용해서 회전등의 작업을 진행합니다.

Brush의 색상을 선언하여 변수를 1개 생성합니다.

 

path.AddLine(115, 115, 110, 125);
path.AddLine(110, 125, 100, 125);
path.AddLine(100, 125, 110, 130);
path.AddLine(110, 130, 105, 140);
path.AddLine(105, 140, 115, 132);
path.AddLine(115, 132, 125, 140);
path.AddLine(125, 140, 120, 130);
path.AddLine(120, 130, 130, 125);
path.AddLine(130, 125, 120, 125);
path.AddLine(120, 125, 115, 115);

 

위에서 선언한 path 변수를 이용하여 AddLine의 시작 좌표(x, y)와 끝 좌표(x, y)를 지정해서 선을 추가합니다.

첫 번째 선은 (115, 115)에서 시작해서 (110, 125)에 끝납니다. 결과의 녹색 별에 해당합니다.

선을 추가만하고 아직 그리지는 않은 상태이니 참고해주세요.

 

graphic.DrawPath(pen, path);
graphic.FillPath(brush, path);
graphic.DrawString("원본", new Font("맑은 고딕", 12), Brushes.Black, new PointF(95, 150));

 

그림을 그릴 graphic 변수에 DrawPath 함수를 이용하여 그릴 펜과 path변수를 넣고

아까 추가한 선들을 그립니다. 해당 함수는 path에다 추가해 놓은것을 한 번에 그립니다.

선을 그린 후 FillPath 함수로 brush와 그렸던 path를 재사용해서 내부를 채웁니다.

그 밑으로는 문자열을 그려줍니다. (텍스트, 폰트, 브러쉬, 글자 위치) 입니다.

글자 위치는 맨 앞 글자를 기준으로 정해지므로 도형과 똑같은 위치 값을 주게되면

글자가 좀 더 오른쪽으로 쏠려보입니다.

 

// 회전 과정 그리기
for (int i = 0; i < 8; i++)
{
    matrix.Translate(100 + (i * 75), 0);
    matrix.RotateAt(45 * (i + 1), path.PathPoints[0]);  // 회전 시 offset 값도 같이 비례해서 바뀜
    graphic.Transform = matrix;  // 회전 적용 후 그린다.

    graphic.DrawPath(pen, path);
    graphic.FillPath(Brushes.Yellow, path);

    matrix.Reset();
    graphic.Transform = matrix;
    graphic.DrawString((45 * (i + 1)) + "˚", new Font("맑은 고딕", 12), Brushes.Black, new PointF(190 + (i * 75), 150));
}

 

다음은 회전 과정을 그립니다. 결과의 노란색 별들에 해당합니다.

수학적으로 한 바퀴는 총 360˚이고 반복문 횟수는 45˚씩 회전하면서 원위치까지 그리기 때문에 8로 설정해줍니다.

 

matrix.Translate(100 + (i * 75), 0)는 maxtix를 적용하는 graphic의 초기 위치를 기준으로 설정한 x, y 값 만큼

이동시키는 역할을 하며, 반복문 진행마다 (100, 0), (175, 0), (250, 0) ... 이 설정되고,

이는 맨 처음 path.AddLine(115, 115, 110, 125)를 기준으로 적용되기 때문에

i = 0 일 때는 (215, 115, 210, 125), i = 1 일 때는 (290, 115, 285, 125) 와 같이 초기값이 설정됩니다.

 

matrix.RotateAt(45 * (i + 1), path.PathPoints[0])maxtix를 적용하는 graphic의 회전율을 설정합니다.

path.PathPoints[0]은 맨 처음 그린 선의 좌표 (115, 115, 110, 125) 입니다.

해당 좌표를 기준으로 45˚만큼 회전을 시킵니다. 반복문이 진행될수록 90˚, 135˚, 180˚... 가 됩니다.

 

RotateAt을 모든 좌표에 적용하지 않는지 궁금한 경우도 있을 것 입니다.

그 이유는 설정한 좌표를 기준으로 적용되는 graphic의 전체를 회전시키기 때문인데요.

쉽게 예를 들면 '그림 가운데에 고정핀을 박고 손으로 그림을 옆으로 돌린다'라고 생각하시면 됩니다.

따라서 모든 좌표에 RotateAt을 적용한다면... '선 하나 그리고 회전하고 또 선 하나 그리고 회전하고'가 됩니다.

그렇게 되면 괴상한 모양이 연출될 것 입니다.

RotateAt을 쓰게 되면 적용된 graphic의 모든 좌표 값이 회전 각도에 맞춰서 변경되니 참고해주세요.

 

graphic의 Transform기능을 이용하여 설정한 matrix를 적용시킵니다.

 

matrix 적용 후 DrawPath 함수로 그리고 FillPath 함수로 내부 색상을 채웁니다.

 

Reset 함수로 적용한 matrix를 초기화하여 다음 회전을 적용하기 위해 준비시킵니다.

초기화한 matrix 정보를 Transform으로 적용하여 원래대로 되돌립니다.

이렇게 해주지 않으면 위치값이 아래와 같이 산으로 가기 때문입니다.

 

matrix 초기화를 적용하지 않은 결과

원본의 별 색상이 다른 것은 색상을 변경 후 실행하였기 때문이니 헷갈리지 마시길 바랍니다.

 

matrix 초기화 후 각 도형 밑에 적용된 각도를 알기 위한 문자열을 그립니다.

현재 코드에선 글자를 반복문 진행에 따라 45˚씩 증가시키고

위치의 x값을 초기 위치인 115를 기준으로 100만큼 이동시킨 도형 밑에 최대한 맞추기 위하여

215에서 살짝 뺀 190으로 잡고 반복문 진행마다 75씩 x값을 증가시켰습니다.

 

// 사각형 1
Rectangle rec = new Rectangle(50, 250, 150, 150);
Rectangle clipRec = new Rectangle(100, 250, 50, 50);
Rectangle clipRec2 = new Rectangle(50, 300, 150, 50);
Rectangle clipRec3 = new Rectangle(50, 350, 25, 25);
Rectangle clipRec4 = new Rectangle(175, 350, 25, 25);

graphic.ExcludeClip(clipRec);
graphic.ExcludeClip(clipRec2);
graphic.ExcludeClip(clipRec3);
graphic.ExcludeClip(clipRec4);

graphic.DrawRectangle(pen, rec);
graphic.FillRectangle(Brushes.Pink, rec);

 

이제 사각형입니다. 결과 화면의 왼쪽 사각형부터 시작하여 오른쪽으로 진행됩니다.

먼저 Rectangle 클래스로 사각형을 생성합니다. (50, 250) 위치가 사각형의 왼쪽 상단 꼭지점 부분이며,

크기는 가로, 세로 둘 다 150으로 설정합니다.

다음은 Clip을 진행할 사각형을 생성합니다. 총 4개입니다.

 

graphic에 ExcludeClip을 이용하여 제외 영역을 설정합니다. 붉은색 사각형 기준으로

clipRec가 검은색 영역

clipRec2이 파란색 영역

clipRec3가 붉은색 영역

clipRec4이 녹색 영역입니다.

 

 

제외 영역을 설정 후 사각형을 그리고 내부의 색상을 채웁니다.

 

// 사각형 2
rec = new Rectangle(250, 250, 150, 150);
clipRec = new Rectangle(250, 250, 50, 50);

graphic.ResetClip();
graphic.IntersectClip(clipRec);

graphic.DrawRectangle(pen, rec);
graphic.FillRectangle(Brushes.LightGreen, rec);

 

2번째 사각형입니다. (250, 250) 위치에 가로, 세로 150 크기로 rec 이라는 사각형을 만듭니다.

다음은 clip 시킬 사각형을 같은 위치에 가로, 세로 50 크기로 생성합니다.

 

위에서 적용된 Clip을 초기화시키고 IntersectClip으로 그릴 영역을 설정합니다.

적용하면 clipRec에 해당하는 부분만 그려지게 됩니다.

 

사각형을 그리고 내부를 채웁니다.

 

// 사각형 3
rec = new Rectangle(450, 250, 150, 150);
clipRec = new Rectangle(500, 250, 50, 50);
clipRec2 = new Rectangle(450, 300, 150, 50);
clipRec3 = new Rectangle(450, 350, 25, 25);
clipRec4 = new Rectangle(575, 350, 25, 25);
Region region = new Region(rec);

graphic.ResetClip();
region.Exclude(rec);

region.Xor(clipRec);
region.Xor(clipRec2);
region.Xor(clipRec3);
region.Xor(clipRec4);

graphic.DrawRectangle(pen, rec);
graphic.FillRegion(Brushes.LightGreen, region);

 

3번째 사각형입니다. (450, 250) 위치에 가로, 세로 150 크기로 rec이라는 사각형을 만듭니다.

사각형1에서 썻던 clip 사각형들과 같은 크기로 좌표만 변경해서 clip용 사각형을 만듭니다.

이번에는 Xor 기능을 적용하기 위하여 Region 클래스로 rec 사각형을 적용하여 변수를 만듭니다.

 

위에서 적용한 Clip을 초기화하고 region 변수에 Exclude를 이용하여 rec 사각형 전체를 빈 공간으로 만듭니다.

 

region 변수의 Xor 기능으로 각 clip 사각형을 적용시킵니다.

Xor은 Bit 연산에서 자주 사용되는 개념인데 1과 0이 만나면 1이 되고 (0, 0), (1, 1)은 0이 되는 구조입니다.

현재 사각형을 기준으로 보면 빈 사각형(0)에 clip 사각형(1)을 추가함으로써 해당 부분을 활성화시키는 것 입니다.

 

적용 후 사각형을 그린 후 내부를 채웁니다.

 

이상으로 Graphics 디자인 관련해서 좀 더 심화된 내용을 알아보았습니다.

WPF보다는 떨어지지만 Graphics 기능을 잘 쓰면 나름 Winform도 디자인을 잘 뽑아낼수 있습니다.

관련글 더보기

댓글 영역