[API] 기말 과제

simpled 2013.06.23 16:12 조회 수 : 6869

api_01.png 

<그림 01 :: Concept' >


200502219 신동석.exe


만든지 6개월만에 올리네요^^;

죄송합니다.


저는 기말 과제를 <그림 01>과 같이 만들어서 '진짜' 그림판과 같이 만들어보고 싶었습니다.

그림판은, 색의 변화, 선 굵기의 변화는 물론, 아주 기본적인 작도 기능도 있어야 합니다.

그리고 Windows의 y좌표 체계를 바꾸어서 아래가 0이 되도록 하여, 사용자의 편의를 최대한 고려했습니다.


api_02.png

<그림 2 :: 그림의 확장>

 

그리고 <그림 2>와 같이 그리기 영역이 확장 될 수 있어야 합니다.

물론 축소 될 수도 있어야합니다.

이를 위해서 사각형(jog)을 '인식' '선택' '이동' 시킬 수 있는 테크닉이 필요합니다.

또한 영역의 크기에 따라 jog가 항상, 위치 선상의 중앙에 위치해야 함은 말할 필요도 없는 사실입니다.

api_03.png 

<그림 3 :: 지우개 >


원래는 그려지는 모든 객체들을 '선택'하게 할 수 있게 하려 했지만, 알고리즘을 생각하기 귀찮아서, '선택'의 자리에 '지우개'를 넣었습니다.

그리고 지우개 기능도 조금 어색해서 나중에는 BRUSH라고 정정한 것 같습니다.



api_04.png api_05.png api_06.png

<그림 4 :: 트리플 버퍼링>

 

사실 이때부터 MFC에 관심이 생기기 시작했던지라, 버퍼를 3개를 사용했습니다.

1. 스왑 버퍼(2). 2. 출력버퍼(1) 이렇게요.

왜 이런 방식을 택했는지는 만드시다 보면 아시게 될 것입니다. 

(솔직히 말해서 지금은 그 구조가 전혀 기억나지 않습니다.)


대충이라도 설명하자면 다음과 같습니다.


1. jog는 이동하지만, 그림을 스쳐지나가지 않는 이상 그림은 보존되어야 하므로, 영역 확장/축소에 따른 버퍼 스왑이 필요함.

즉, 변화전 저장 비트맵, 변화후 비트맵 이 있어야 함.

2. 출력만 하는 비트맵이 있어야함. 물론 그리기 영역만큼 cut 되겠지만. 



api_07.png

<그림 5 :: 모달리스 구현>




api_08.png


<그림 6 :: 모달리스 다이얼로그>

 

그리고 옵션 기능으로 모달리스 다이얼로그를 넣었습니다.


이 기능은 비하인드 스토리를 갖고 있는데요, 

사실 저는 C언어를 마스터하면서, 그와 같이 객체에 대한 회전, 스캐일링, 이동 알고리즘에 대한 공부를 끝낸 상황이였습니다.


그래서 API 학기 초중반부터 타원을 그리는 알고리즘에 대한 공부를 시작하였고, 

행렬계산을 통한 베지에커브, 넓스커브, 스플라인커브 등을 그릴 수 있는 프로그램을 만들고자 했습니다.

 

하지만 중간과제의 limits때문에 이내 곧 좌절하고 말았는데, 그것은 바로 코드를 100줄 이내로 작성하라는 것이였습니다. 

(저는 이미 그 당시 3000줄이 넘어가는 코딩을 하고 있었습니다.)


당시 코드는 타원을 그리는 알고리즘까지 완성이 되어 있었습니다.

그거라도 어떻게 써볼라고 몇날 몇일을 날밤을 새가면서 3000줄을 100줄로 만들어보려고 안간힘을 썼지만 결국 포기하였습니다.

그리고 중간과제로 잉여프로그램을 제출했었죠. 그런데 기말 과제는 코드 제한이 없었고, 표현이 그만큼 더 자유로워졌습니다.

그래서 학생들의 수준이 높다 싶으면 히든카드로 빼서 발표할 생각으로 만들어 보았습니다. (생각보다 수준이 높아서, 결국 발표를 하게 되었습니다.)


api_10.png

<그림 7 :: 그리기>



api_09.png 

<그림 8 :: CAD sys' 프로그램>


<그림 7>은 <그림 6>의 모달리스가 변형된 모습을 보여주고 있습니다.

개인적으로 다이얼로그<->다이얼로그 방식은 너무 재미가 없어서, 그냥 윈도우를 하나 더 띄우는 방식으로 바꾸었습니다.

(중복 실행을 방지하기 위해, 전역 불리안 변수가 해당 창의 on, off 를 감지합니다. )


<그림 8>은 Cad sys' 에서 타원 하나 그려진 모습을 보여주고 있습니다.

코드를 변경하면 더 많이 그릴 수 있습니다만, 많이 그리는것이 목적이 아니라 제대로 된 타원의 컨트롤이 목적이므로, 편의상 3개만 그리도록 하였습니다.


<그림 7>을 보면 알겠지만, 타원 주변으로 총 8개의 조그가 있습니다.

또한, 타원이 그려지는 모습에서 타원은 검정색을 띄고 있습니다.


api_11.png

<그림 9 :: 선택>


타원을 선택하게 되면 <그림 9>와 같이, '선택' 모드가 활성화 됩니다.

직접 실행시켜보시면 알겠지만, API 인식 시스템(PtInRect)와는 다르게, 마우스 포인터가 타원 안에 반드시 들어가야 타원이 선택됩니다.

(가끔 안될 때도 있는데, 그것은 행렬계산의 오차 때문입니다.)

무슨 말이냐면, 타원을 사각형으로 인식하지 않고, 타원 그 자체로 인식하고 있다는 말입니다.


api_12.png 

<그림 10 :: 늘이고, 줄이기>


타원이 선택 되었다면 <그림 10>과 같이 상,하,좌,우의 조그를 통해 타원의 외형을 늘이고 줄일 수 있습니다.



api_13.png 

<그림 11 :: 회전>

 

그리고 <그림 11>과 같이 중심을 기준으로 회전 시킬 수 있습니다. 

절대좌표(0, 0)을 기준으로 회전하지 않고 있습니다.

타원은 항상 상대좌표(타원의 중심)를 기준으로 회전합니다.

api_14.png

<그림 12 :: 스트랫츼>

 

물론 회전 후에도 스트랫치 기능이 잘 먹여야 겠죠?



api_15.png

<그림 13 :: Drag&Drop 방식의 이동이 가능합니다.>


또한 이동도 가능합니다.

부수적으로, '영역 자동 인식 기능' 과 '영역 자동 회전 기능'이 있는데, 그것은 실행시켜보시면서 체험하시기를 권장합니다. ^^



마지막으로 설명 드릴 것은 '타원의 인식' 알고리즘입니다.


타원을 어떻게 인식 시킬 것입니까?


요컨데 저는 가장 맨 처음으로 타원을 그리는 함수를 거리값의 최대값부터 거리값의 0값까지 촘촘하게 모든 pixel을 검사하여 좌표를 확인시켰습니다.

그러다 보니 엄청난 시간이 걸렸죠...그리고 타원이 2개 3개로 늘어나면 그 시간이 n승으로 늘어나더군요.


(흥미로운 사실도 알아냈는데요, for문은 증가문보다 가감문이 더 빠르다는 사실입니다. 

정확한 원리는 잘 모르겠지만, 엄청난 양의 pixel을 검사하는 이 계산에서

그 속도가 확실이 2배 이상의 차이가 난 것 같습니다.)


두번째로 시도한 것은 API처럼 그냥 사각형으로 인식 시키는 것입니다.


세번째로 시도한 것은 후배가 준 아이디어인데, 

타원 중심에서부터 마우스로 찍은 거리와, 타원 중심으로부터 같은 각도에 위치한 타원의 외곽과의 거리를 비교하는 것입니다.

회전의 요소가 들어갔으면 회전 각도만큼 타원 좌표와 마우스 좌표를 역회전 시키고 검사 한 후, 다시 회전 각도만큼 회전시키는 방식을 택하게 했습니다.

이를 사용하여 타원 인식의 속도를 정말 비약적으로 상승 시켰습니다.


쉽게 말해, 

마우스로 임의의 좌표를 찍었다면, 타원 중심으로부터의 벡터 거리가 나오잖아요? 

이 거리값을 R1이라고 하겠습니다.

그리고 벡터 방향을 참고하여 타원의 중심과 타원의 외곽과의 거리를 도출합니다.

이 거리값을 R2라고 하겠습니다.

그리고 R1과 R2를 비교하기만 하면 됩니다.


이것은 아마도 타원 내부의 픽셀을 모조리 검사하는 것보다 수십, 수백 배 빠르고 안정성 있는 방법일겁니다.


단, R2를 계산함에 있어서 타원 행렬 계산의 오류를 만나 볼 수 있습니다.

타원을 그리는 행렬식을 통하여 R2를 구할 수 없기 때문입니다.
조금 다른 시각으로 타원을 바라볼 필요가 있습니다.

조금만 생각해보시면 되니, 굳이 이 방법까지 기재하지는 않겠습니다.