글목록

2022년 3월 22일

도형 변환하기 - 5. 자유형 도형(msoFreedom)의 꼭지점 좌표 구하기

Powerpoint를 도형을 생성하게 되면 아래와 같이 직선 및 곡선 Segment를 조합하여 그림을 그립니다. 직선형인 경우, P1, P2, P3.. 점들은 꼭지점이 되고, 각 꼭지점을 연결한 직선으로 구성이 됩니다. 그러나, 곡선인 경우, P1~P4가 1개의 Bezier 곡선을 그리고, P4~P7이 다음 Bezier 곡선을 그려서 연결하는 방식으로 도형을 만들어 나갑니다. 따라서, 순수한 직선형인 경우, n개의 Segment가 있으면 n+1개 점(node)의 위치 정보만 있으면 되고, 순수한 곡선형인 경우, n개의 Segment를 표현하기 위해 3n-2개 점의 위치 정보가 있으면 되며, 점들의 위치 정보만으로도 도형을 완벽하게 재현해낼 수 있습니다.

그러나, 경우에 따라서는 곡선형 Segment와 직선형 Segment가 연속되어있는 경우도 있으며, 연속된 점들의 정보만으로는 직선인지, 곡선인지 구분할 수 없기 때문에, Segment의 특성을 직선형 또는 곡선형으로 정의해 주어야 합니다. 따라서, 각 점의 위치 정보만으로 도형을 완벽하게 재현하지 못합니다.





곡선형인 경우, P1, P4, P7... 의 점은 Bezier 곡선을 그릴 때, 시작 및 끝점(End point)에 해당하고, 이 점들은 자유형 그림을 그릴 때, 사용자가 마우스로 클릭해주는 위치입니다. P2, P3, P5, P7은 곡선형 도형을 생성할 때 Powerpoint에서 자동으로 삽입해주며, 도형을 클릭해서 '점편집' 메뉴를 이용하여 인위적으로 변경할 수 있습니다. 이 점들은 곡선의 꺾인 정도나 모양을 결정해주는 점들이며 Bezier 곡선의 Control Point라고 합니다.

만약, 곡선형 도형의 점들 중, 3n+1번째 점들만 남겨놓고 나머지 점들을 지워버린다면 P1, P4, P7... 의 정보만 남게 되며, 점들만 선으로 연결하면, 직선형으로 바뀌게 됩니다.

반대로, P1, P2, P3... 의 직선형 도형에 인위적으로 Control Point를 삽입해주면 원하는 형태의 곡선형 도형으로도 바꿔줄 수 있습니다. 예를 들어, Powerpoint에서 원은 마름모 모양으로 배열된 4개의 End point와 원이 될수 있도록 적절한 위치에 Control Point를 삽입해준 형태이며, Control Point를 삭제하게 되면 다시 마름모가 됩니다.


도형의 모든 점들의 위치 정보는 Shape.Vertices 속성으로부터 쉽게 얻을 수 있습니다. 그러나, Vertices 속성은 msoFreedom 형식, 즉 자유형 도형에만 지원하기 때문에 도형 템플릿으로 그린 도형은 바로 구할 수 없습니다. 따라서, 도형이 가진 점들의 위치 정보를 얻기 위해서는 이전 글에서 소개해드린 함수를 이용하여 자유형 도형으로 변환한 후 Shape.Vertices 속성으로 구하게 됩니다.


점에 대한 정보를 구하는 목적은 해당 점들을 이용해서 새로운 도형을 생성하거나, 점을 새로 배치해서 도형을 변형하는 등의 작업을 하기 위해서 입니다. 만약 도형에 점을 추가하고 싶다면, Shape.Nodes.Insert를 사용할 수 있습니다. 직선형이나 곡선형 모두 추가할 수 있고, 점의 위치만 정확하게 지정해주면 됩니다. 1~2개 점은 이런 식으로 추가할 수 있지만, 도형에 포함된 node 갯수가 증가할수록 실행 속도는 점점 느려집니다. 100여개 정도의 점을 추가해주려고 하니 PC 사양에 따라서는 몇 분 이상이 걸리기도 합니다. 따라서, Node가 많은 여러 개의 도형에 대해 작업하려고 한다면, 작업 시간이 매우 느려지는 문제가 있습니다. (freehand 형태의 도형으로 작업하다보면, 포함되는 node 갯수가 많기 때문에, 때로는 Powerpoint가 멈춰버리는 문제도 생깁니다.)

한편, 모든 점들에 대한 정보만 있다면, 직선형인 경우 Slide.Shapes.AddPolyLine(좌표배열), 곡선형인 경우 Slide.Shapes.AddCurve(좌표배열)을 이용하여 한꺼번에 도형을 생성하게 되면 상대적으로 실행속도가 매우 빠른 것을 알 수 있습니다. 따라서, 처리해야할 점의 갯수가 많다면, 점들의 정보를 모두 읽어와서, 이 점들의 정보로부터 새로운 좌표배열을 생성한 후, 완전히 새로운 도형으로 생성하는 것이 속도면에서는 훨씬 빠릅니다. 다만, AddPolyLine이나 AddCurve는 도형의 모든 node가 직선형이거나 곡선형이어야 한다는 제약이 있습니다.

따라서, 도형의 좌표를 읽어올 때, 처음부터 곡선형 좌표 또는 직선형 좌표 배열 형식으로 읽어오도록 함수를 만들어 둡니다.

아래에 도형(Drawing)의 좌표배열을 구하는 함수를 생성해두었습니다. 앞으로 슬라이드에 간단히 생성한 도형을 변형하거나, 배열하는데 있어 이러한 점 좌표 배열을 이용할 예정입니다.


'---------------------------------------------
Function GetVertices(iSh As Shape, Optional iDelControl As Boolean = False) As Single()
  On Error GoTo ErrorHandler
  Dim tSh As Shape, tP() As Single, tPX() As Single, tPY() As Single, i As Long, j As Long, n As Long
  '만약 입력된 도형이 msoFreedom 형식이 아니라면, msoFreedom으로 변환합니다.
  If iSh.Type = msoFreeform Then 
    Set tSh = iSh
  Else 
    Set tSh = Sh_ConvAutoShape(iSh, False)
  End If

  '도형으로부터 좌표 배열을 읽어옵니다. (1 to n, 1 to 2) 배열이며, (n, 1)에는 n번째 점의 X좌표가, (n, 2)에는 n번째 점의 Y좌표가 입력됩니다. 여기에서 X좌표는 슬라이드 왼쪽 끝으로부터 거리이며, Y좌표는 슬라이드 위쪽 끝으로부터 거리입니다.
  tP = tSh.Vertices
  n = 1
  ReDim Preserve tPX(1 To 1): ReDim Preserve tPY(1 To 1)
  tPX(1) = tP(1, 1): tPY(1) = tP(1, 2)

  '1번점은 시작점이며, 2번째 node의 종류에 따라 좌표를 읽어옵니다. 만약, Control Point를 모두 삭제하고 꼭지점에 해당하는 End Point만 읽어오려고 한다면, 직선형 Node는 바로 다음 점을, 곡선형 Node는 2번째 뒤의 점을 읽어 옵니다. 이렇게 읽은 점 배열은 이후 AddPolyLine으로 다시 그리게 되면, 다각형이 생성됩니다.
  If iDelControl Then
    For i = 2 To tSh.Nodes.Count
      n = n + 1
      ReDim Preserve tPX(1 To n): ReDim Preserve tPY(1 To n)
      If tSh.Nodes(i).SegmentType = msoSegmentLine Then
        tPX(n) = tP(i, 1): tPY(n) = tP(i, 2)
      Else
        i = i + 2
        tPX(n) = tP(i, 1): tPY(n) = tP(i, 2)
      End If
    Next
    ReDim tP(1 To n, 1 To 2)
    For i = 1 To n: tP(i, 1) = tPX(i): tP(i, 2) = tPY(i): Next
  Else
  '만약, 모든 점을 곡선형의 좌표배열로 만들어서 출력합니다. 만약, 해당 Node가 직선형이라면, 이전 점의 좌표와 현재 점의 좌표를 Control Point로 추가해주며, 나중에 AddCurve로 그리더라도 직선으로 표현이 됩니다. 만약, 해당 Node가 곡선형이라면 좌표를 그대로 읽어옵니다.
    For i = 2 To tSh.Nodes.Count
      n = n + 3
      ReDim Preserve tPX(1 To n): ReDim Preserve tPY(1 To n)
      If tSh.Nodes(i).SegmentType = msoSegmentLine Then
        tPX(n - 2) = tP(i - 1, 1): tPY(n - 2) = tP(i - 1, 2)
        tPX(n - 1) = tP(i, 1): tPY(n - 1) = tP(i, 2)
        tPX(n) = tP(i, 1): tPY(n) = tP(i, 2)
      Else
        i = i + 2
        tPX(n - 2) = tP(i - 2, 1): tPY(n - 2) = tP(i - 2, 2)
        tPX(n - 1) = tP(i - 1, 1): tPY(n - 1) = tP(i - 1, 2)
        tPX(n) = tP(i, 1): tPY(n) = tP(i, 2)
      End If
    Next
    ReDim tP(1 To n, 1 To 2)
    For i = 1 To n: tP(i, 1) = tPX(i): tP(i, 2) = tPY(i): Next
  End If
  GetVertices = tP
  '만약, 입력된 도형이 msoFreedom 형식이 아니라면, 좌표를 읽기 위해 생성시킨 tSh를 삭제합니다. 
  If Not iSh.Type = msoFreeform Then tSh.Delete
ErrorHandler:
  Erase tP, tPX, tPY
End Function


많이 본 글 :