프날 오토핫키 강좌
누르면 강좌 리스트가 나와요
프날 오토핫키 강좌

⚠ 이 강좌는 오토핫키 v1을 다룹니다

지금 보시는 강좌는 구버전 오토핫키(v1.1)를 다루고 있습니다. 따라서 본 강좌의 내용은 현재 최신 오토핫키 버전 (v2.0)과 호환되지 않습니다. 구버전의 정보가 필요한 것이 아니라면, 가능한 한 새로운 사이트에 작성한 v2 강좌(https://ahkv2.pnal.dev)를 봐주시길 바랍니다.

[프날 오토핫키] GDI+ (gdip) #8: 그래픽 객체와 도형 그리기

이번 강에서는 그래픽 객체의 개념을 알고, 이를 이용해서 GUI에 도형을 그려보도록 하겠습니다.

마우스를 이용하여 직접 도형을 그리는 것이 아님을 유의해주시고, 도형을 화면에 출력하는 과정을 다룹니다.

물론, 응용하면 마우스를 이용하여 그리는 것도 가능합니다. 그림판처럼요!

 

  • Gui 개념을 정확히 아시는 분들을 위한 강좌입니다. 특히 Picture 컨트롤과 GuiControl은 확실히 알아두세요.
  • 지난 강 까지의 과정을 모두 이해하셔야 이번 강을 잘 이해하실 수 있습니다.
  • 평소보다 외워야 하는 과정과 함수가 많습니다. 

 

완성품 미리보기


 그래픽 객체 

도형을 그리기 전에, 그래픽 객체(Grphics Object)에 대해 먼저 알아야합니다. 그래픽 객체는 화면에 무언가를 '그릴 때' 필요한 기능을 모아둔 일종의 모음집입니다. GDI+에선 이 그래픽 객체를 이용하여 화면상에 도형을 그릴 수 있도록 합니다.

 

직접 GDI+가 처리해도 될 것 같은데, 굳이 그래픽 객체를 쓰는 이유는 여러개의 도형을 하나로 묶어 둘 장치가 필요했기 때문이고, 또한 그렇게 묶은 '도형 세트'에 다양한 효과 (예를 들면 품질 조정, 변형, 자르기 등)를 주기 위해서입니다.

 

아무튼 무언가 화면에 '그릴 때'는 그래픽 객체가 필요하다는 점만 알아두세요. 물론, gdip을 이용해서 만들 수 있습니다.


 GDI 객체 

GDI 객체란 GDI에 들어있는 각종 '그리기' 도구입니다. 여러분이 흔히 그림 도구하면 생각나는 펜, 브러쉬, 팔레트 등을 포함하고요. 더 나아가 폰트, 비트맵 또한 GDI 객체입니다.

 

지금껏 강좌에서는 비트맵만을 써왔는데, 이제는 '도형'을 그릴 GDI 객체, 즉 펜 또는 브러쉬 또한 사용합니다. 우리가 실제로 직선을 긋기 위해 펜을, 사각형을 채우기 위해 브러쉬를 사용하는 것처럼요.


 도형을 그리는 과정 

우리는 오토핫키에서 쓰는 GUI 컨트롤 중 'Picture' 컨트롤에 도형을 출력해볼 것입니다. 제가 생각할 때 제일 간단한 방법입니다. Picture 컨트롤은 비트맵을 출력할 수 있거든요. 물론 바로 pBitmap을 이용해서 출력하는 것은 안되고요, pBitmap을 다시 비트맵의 핸들로 바꿔주는 작업이 필요합니다. Picture 컨트롤은 비트맵 핸들을 통해 비트맵을 출력할 수 있어요. (추후 설명)

 

[Picture 컨트롤에 도형 그리기]

1. GUI에 Picture 컨트롤 생성
2. 빈 비트맵 생성. 도화지의 역할을 할 것입니다.
3. 그래픽 객체 생성
4. 펜 생성
5. 그래픽 객체와 펜을 이용해서 (2)에서 만든 빈 비트맵에 도형을 그린다
6. 그려진 비트맵의 핸들 가져오기
7. 비트맵 핸들을 이용하여 Picture 컨트롤에 출력

복잡하지만 막상 따라해보면 쉽습니다. 잘 따라와주세요. 이제 GUI에 도형을 출력해볼 것입니다.


 1. GUI 생성 

도형을 출력할 GUI를 생성하겠습니다. 저는 아래와 같이 구현해보았습니다. Gdip도 include해줄게요.

#Include Gdip_All.ahk
Gui, Add, Picture, x0 y0 w300 h300 vPic,
Gui, Show, w300 h300, test
return

GuiClose:
ExitApp

생성된 GUI입니다.

사진으로 봤을 땐 빈 창 같지만, 사실 GUI와 똑같은 크기의 Picture 컨트롤이 있습니다. 아무 사진도 띄워지지 않은 상태일 뿐이지요.


 2. 그리기 준비 

도형을 출력하기 위해 필요한 것들로는 '비트맵' '그래픽 객체' '펜'이 있습니다. 셋 모두 생성해보겠습니다.

 

가. 비트맵

비트맵은 지금까지 했던것처럼 Gdip_CreateBitmapFromFile() 함수를 써도 되지만, 이번엔 '비어있는 비트맵', 즉 크기 정보만 가지고 있는 비트맵을 만들어보겠습니다.

pBitmap := Gdip_CreateBitmap(300, 300)

매개변수로는 만들 비트맵의 너비와 높이를 써주었습니다. 300x300 크기의 빈 비트맵을 만드는 함수인거죠. 크기는 위에서 만든 Picture 컨트롤의 크기와 동일하게 만들어주세요.

 

만약 여러분들이 빈 비트맵이 아니라 이미 있는 비트맵을 준비할 경우, 도형은 이미 있는 그림 위에 그려집니다. 이를 이용하면 간단한 사진 편집 프로그램 정도는 만들 수 있겠지요. (기능이라곤 '사진 위에 그리기' 뿐일겁니다.)

 

나. 그래픽 객체

이번엔 그래픽 객체를 생성해보겠습니다. 특히, 이번엔 비트맵을 그래픽 객체에 연동시킬것이기 때문에 아래와 같은 함수를 이용해주어야합니다.

G := Gdip_GraphicsFromImage(pBitmap)

이렇게 쓰신다면 비트맵을 이용하여 그래픽 객체를 생성할 수 있습니다. pBitmap은 위에서 준비했던 빈 비트맵 혹은 따로 준비하신 비트맵입니다.

 

다. 펜

마지막으로 펜을 만들어보겠습니다. 우리가 그림을 그릴 때와 마찬가지로, 펜은 도형의 두 가지 속성을 담당합니다. 바로 "색"과 "두께"입니다.

pPen := Gdip_CreatePen(0xff00C896, 4)

위 구문은 0xff00C896 색상을 가진 두께 4의 펜을 만들어서, pPen 변수에 담는 구문입니다. 변수명에서 알 수 있듯이 만들어진 펜의 메모리 주소가 담기고요, 여기서 색상을 늘 쓰던 (0x00C896와 같은)6자리 수가 아닌 8자리 수로 써준 이유는 "RGB"가 아닌 "ARGB"로 색상을 써야하기 때문입니다. A는 투명도이며, 보통은 ff(완전 불투명)으로 써줍니다.

 

따라서 제가 만든 펜은 0x00C896 색상과 동일합니다.

 

이쯤에서 펜의 사용처와 브러쉬의 사용처를 구분해보겠습니다. 펜은 '너비'가 반드시 필요한 도형을 그릴 때 필요합니다.

테두리가 있는 사각형, 직선, 테두리가 있는 원 등입니다.

반면 브러쉬는 너비를 사용하지 않고, 색상값만 있습니다. 채워진 사각형, 채워진 원 등을 그릴 때 필요하니 참고해주세요. 브러쉬는 아래 함수로 만들 수 있습니다.

pBrush := Gdip_BrushCreateSolid(색상값)

색상 값이 펜과 같이 16진수 ARGB 색상을 써주면 됩니다.

강좌에서는 일단 펜을 사용하여 직선을 그리는 것을 목표로 하겠습니다.


이 구문들을 우리가 만든 GUI에 옮겨보겠습니다. 저는 핫키를 이용해서 A키를 눌렀을 때 도형이 그려지도록 만들어볼게요. (스크립트에선 그래픽 객체를 펜 생성 구문 뒤에 써주었는데, 상관 없습니다.)

#Include Gdip_All.ahk
Gui, Add, Picture, x0 y0 w300 h300 vPic,
Gui, Show, w300 h300, test
return

A::
pToken := Gdip_StartUp()
pBitmap := Gdip_CreateBitmap(300, 300)
pPen := Gdip_CreatePen(0xff00C896, 4)
G := Gdip_GraphicsFromImage(pBitmap)
return

GuiClose:
Gdip_Shutdown(pToken)
Gdip_DisposeImage(pBitmap)
Gdip_DeletePen(pPen)
Gdip_DeleteGraphics(G)
ExitApp

GDI+ 초기화와 정리 구문을 넣어주었습니다. 또한 펜과 그래픽 객체를 정리하는 구문인 Gdip_DeletePen()과 Gdip_DeleteGraphics()구문 또한 써주었습니다. 이 정도는 눈치껏 이해하실 수 있을거예요.


3. 도형 그리기

이제 도형을 그려보겠습니다. 우리가 준비했던 비트맵, 펜, 그리고 비트맵으로 만든 그래픽 객체를 이용하면 단 하나의 함수로 도형을 그릴 수 있습니다.

 

저는 직선을 그려보겠습니다.

Gdip_DrawLine(G, pPen, 10, 10, 280, 280)
  • G: 비트맵으로 만든 그래픽 객체입니다.
  • pPen: 펜의 메모리 주소입니다.
  • x1, y1, x2, y2: 직선이 그려질 처음과 끝입니다. 예제에서는 10,10부터 280,280까지 직선이 그어집니다.

이와 같이, 여러 도형 함수가 있으니 입맛에 맛게 그려주시면 됩니다.

GDI 객체 함수명 매개변수
Gdip_DrawRectangle() 그래픽 객체, 펜, x, y, w, h
Gdip_DrawRoundedRectangle() 그래픽 객체, 펜, x, y, w, h, 곡률
Gdip_DrawEllipse() 그래픽 객체, 펜, x, y, w, h
Gdip_DrawLine() 그래픽 객체, 펜, x1, y1, x2, y2
브러쉬 Gdip_FillRectangle() 그래픽 객체, 브러쉬, x, y, w, h
브러쉬 Gdip_FillRoundRectangle() 그래픽 객체, 브러쉬, x, y, w, h, 곡률
브러쉬 Gdip_FillPolygon() 그래픽 객체, 브러쉬, 꼭짓점의 수
브러쉬 Gdip_FillEllipse() 그래픽 객체, 브러쉬, x, y, w, h

이외에도 많지만, 나머지는 gdip 라이브러리 내부를 참고해주세요.

 

여기까지 마치셨으면 여러분은 아래와 같은 코드를 가지고 있을것이고, 여러분이 처음에 만들었던 비트맵인 pBitmap에 도형이 그려진 상태입니다. 이제 비트맵을 출력하기만 되는 것이죠

#Include Gdip_All.ahk
Gui, Add, Picture, x0 y0 w300 h300 vPic,
Gui, Show, w300 h300, test
return

A::
pToken := Gdip_StartUp()
pBitmap := Gdip_CreateBitmap(300, 300)
pPen := Gdip_CreatePen(0xff00C896, 4)
G := Gdip_GraphicsFromImage(pBitmap)
Gdip_DrawLine(G, pPen, 10, 10, 280, 280)
return

GuiClose:
Gdip_Shutdown(pToken)
Gdip_DisposeImage(pBitmap)
Gdip_DeletePen(pPen)
Gdip_DeleteGraphics(G)
ExitApp

4. 비트맵 출력

여러분의 비트맵에는 이미 도형이 그려진 상태이기 때문에, Gdip_SaveBitmapToFile()함수를 이용하여 파일로 저장해보시면 도형이 그려진 이미지를 직접 보실 수 있습니다.

 

그렇지만 우리는 GUI에 도형을 출력하는 것이 목표이기 때문에, Picture 컨트롤을 이용하여 도형을 출력해보겠습니다.

 

Picture 컨트롤은 원래 이미 존재하는 이미지 파일을 출력하는 역할입니다. 그렇지만 아직 파일화 되지 않은 비트맵 형태 또한 출력할 수 있습니다. 우리는 지금 pBitmap변수에 비트맵의 메모리 주소를 가지고 있고, 이 비트맵을 파일 생성 없이 직접 Picture 컨트롤에 출력할 수 있다는 뜻입니다.

 

방법은 간단합니다. 비트맵 핸들의 이름을 hBitmap이라고 했을때, Picture 컨트롤의 내용을 HBITMAP:%hBitmap%과 같이 써주시면 되기 때문입니다. 그러면 우선 비트맵의 핸들을 구해야겠죠. GDI+에서는 비트맵의 메모리 주소를 이용하여 비트맵의 핸들을 구할 수 있는 기능이 제공됩니다.

 

pBitmap을 가지고 hBitmap을 만들 수 있다는 뜻입니다.

hBitmap := Gdip_CreateHBITMAPFromBitmap(pBitmap)

함수명이 기니까 오타에 유의해주세요.

 

이제 이렇게 가져온 hBitmap을 이용하여, Picture컨트롤에 출력하면 끝입니다.

Guicontrol, , pic, HBITMAP:%hBitmap%

pic은 제가 지정한 Picture 컨트롤의 v레이블입니다. Picture 컨트롤의 내용을 일반적으로 경로(예: C:\File.png)로 사용하실텐데, 이렇게 HBITMAP:을 앞에 붙여주시면 비트맵의 핸들로도 출력할 수 있습니다.

 

GuiControl 뒤에 콤마가 두개 써진 것은 오타가 아닙니다. 첫 번째 매개변수를 생략했기 때문에 콤마가 두 개 있는 것처럼 보일 뿐입니다.

 

여담이지만, 위와 같이 써줄 경우 hBitmap을 한 번 사용해주면 다시 사용이 불가능합니다. 물론, 다시 pBitmap으로부터 hBitmap을 만들어줘야겠지요. 그렇지만 이건 비효율적인 방법입니다.

따라서, 재사용할 비트맵 핸들일 경우엔 아래와 같이 별표(*)를 콜론(:)뒤에 써주시면 비트맵 핸들을 사용 한 뒤에도, 해당 핸들이 남아있습니다.

Guicontrol, , pic, HBITMAP:*%hBitmap%

 완성된 스크립트 

이제 모든 스크립트가 완성되었습니다. 다소 길었지만 어려운 내용은 없었습니다.

#Include Gdip_All.ahk
Gui, Add, Picture, x0 y0 w300 h300 vPic,
Gui, Show, w300 h300, test
return

A::
pToken := Gdip_StartUp()
pBitmap := Gdip_CreateBitmap(300, 300)
pPen := Gdip_CreatePen(0xff00C896, 4)
G := Gdip_GraphicsFromImage(pBitmap)
Gdip_DrawLine(G, pPen, 10, 10, 280, 280)
hBitmap := Gdip_CreateHBITMAPFromBitmap(pBitmap)
Guicontrol, , pic, HBITMAP:%hBitmap%
return

GuiClose:
Gdip_Shutdown(pToken)
Gdip_DisposeImage(pBitmap)
Gdip_DeletePen(pPen)
Gdip_DeleteGraphics(G)
ExitApp

Test.ahk
0.00MB

 

스크립트를 실행한 후 A키를 누르면 대각선 아래로 직선이 그어지는 것을 확인할 수 있습니다. 정확히는 빈 비트맵에 직선을 그려서, 해당 비트맵을 Picture 컨트롤에 출력한 것이지요. 직선의 성격(색, 두께)을 조절하실려면 펜을 다르게 만드시면 됩니다.

잘 그어집니다.

여러분은 사각형이나 속이 채워진 원과 같은 도형들도 출력해보세요.

그리고 이 상태에서 WinSet 명령어를 사용하여 뒷 배경을 투명하게 하면 화면에 빈 선만 떠있겠지요. 이건 응용의 영역이니 따로 설명드리지 않고, 스크립트만 올리겠습니다.

Test_Trans.ahk
0.00MB


 정리 

전체적인 과정을 한번 더 점검해보겠습니다.

  1. 먼저, 그래픽을 출력할 Picture 컨트롤을 만듭니다.
  2. 비트맵을 준비합니다. 이는 도화지 역할을 합니다.
  3. 아까 만든 비트맵을 이용하여 그래픽 객체를 만듭니다.
  4. 펜을 만들어줍니다.
  5. 그래픽 객체와 펜을 이용하여 비트맵에 도형을 그립니다.
  6. 비트맵의 핸들을 가져온 후, 이를 이용해 Picture 컨트롤에 출력합니다.

분량이 많습니다. 한번 더 처음부터 짜보시면 금방 익숙해질것입니다.

그리고 Dispose와 Delete와 같은 리소스 정리 구문은 반드시 써주세요. 스크립트에 써두었습니다.


 이번 강에서의 gdip 함수 

Gdip_CreateBitmap()

매개 변수: 너비, 높이
반환 값: 빈 비트맵의 메모리 주소
Gdip_CreatePen()

매개 변수: 색상(ARGB), 두께
반환 값: 생성한 펜의 메모리 주소
Gdip_GraphicsFromImage()

매개 변수: 그래픽 객체를 만들 비트맵의 메모리 주소
반환 값: 그래픽 객체
Gdip_Draw****
Gdip_Fill****

본문 참조
Gdip_CreateHBITMAPFromBitmap()

매개 변수: hBitmap을 가져올 pBitmap
반환 값: hBitmap
Gdip_DeletePen()

매개 변수: 펜의 메모리 주소
Gdip_DisposeGraphics()

매개 변수: 그래픽 객체

여담으로, 이번 편을 끝으로 정기적인 gdip 강좌는 일단 마무리 짓도록 하겠습니다. 여러분이 gdip을 찾는 대부분의 이유인 화면 캡처, 비활성 이미지서치, 도형 그리기 등이 끝났고, 이제는 지엽적인 내용밖에 남지 않았기 때문입니다. 물론, 우리가 제대로 GDI+를 이용한 것은 아닙니다. gdip 라이브러리를 사용하는 방법을 배운 것이지요. 그렇지만 더욱 원론적이고 심화적인 내용은 강좌할 계획이 없고, 지엽적이고 가벼운 부분만 틈틈히 올릴 예정입니다.