일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- 인공지능
- Google App Engine
- 삼성소프트웨어멤버십
- 고려대학교
- 가상화
- 하이퍼바이저
- 갤럭시탭S8울트라
- hopfield network
- 파이썬
- BAM
- 빅데이터
- Friendship
- Python
- 신경회로망
- SSM
- 물걸레로봇청소기추천
- 패턴 인식
- Bidirectional Associative Memory
- Neural Network
- 멤버십
- 물걸레자동세척로봇청소기
- 동아리
- 삼성
- 나르왈프레오
- 신경망
- NarwalFreo
- 패턴인식
- 증강현실
- 삼성전자 소프트웨어멤버십 SSM
- 구글 앱 엔진
- Today
- Total
정보공간_1
[4기 신촌 김형진] 윈도우를 캡쳐하는 서로 다른 두 가지 방법 본문
안녕하세요.
신촌멤버십 22-2기 김형진입니다.
이번에는 지난 글에 이어서 Windows API를 이용하여 다른 윈도우를 캡쳐하는 두 가지 방법을 소개하겠습니다.
C++ 기준으로 작성하였으며, C# 등 Windows API를 사용 가능한 언어에서도 같은 방법으로 적용 가능합니다.
1. 첫 번째 방법 : DC
먼저 다른 윈도우의 DC(Device Context)를 통해 얻어오는 방법입니다.
DC를 얻는 방법은 간단합니다. 내가 만든 프로그램의 경우는 WM_PAINT 메시지에서 BeginPaint 함수로 얻을 수도 있으며, WM_PAINT가 아니거나 다른 프로그램의 경우 HWND값을 이용하여 GetDC만 호출하면 얻어올 수 있습니다.
사용법은 간단합니다.
HWND hwndCalc = FindWindow(NULL, "계산기");
HDC hdcCalc = GetDC(hwndCalc);
이렇게 하면 hdcCalc 변수에는 계산기의 DC값이 저장됩니다.
하지만, 여기서 얻은 DC는 Client Area만의 DC입니다. 즉, 화면에 무엇인가를 그릴 수 있는 영역뿐, 타이틀바, 경계선, 메뉴바가 제외된 영역을 얻어오게 됩니다.
따라서, 윈도우의 DC를 얻기 위해서는 GetDCEx 함수나 GetWindowDC 함수를 이용해야 합니다. GetDCEx는 윈도우 외에도 DC를 얻는 여러 다양한 방법을 제시하고 있지만 사용이 복잡합니다. 하지만 GetWindowDC는 GetDC와 용법이 같아 다음과 같이 사용하기만 하면 됩니다.
HWND hwndCalc = FindWindow(NULL, "계산기");
HDC hdcCalc = GetWindowDC(hwndCalc);
DC를 얻었다면 이를 캡쳐하는 방법은 간단합니다.
바로 DC간에 고속으로 복사하는 BitBlt, StretchBlt 함수를 이용하면 됩니다. 이 중 StretchBlt 함수는 그림을 확대/축소하는 기능이 포함되어 있으므로, 일반적으로는 BitBlt 함수를 이용합니다.
또한 BitBlt, StretchBlt 함수는 비트 연산 기능이 포함되어 있지만, 단순 복사를 위해서는 마지막 인자인 Flag를 SRCCOPY로 넘겨 주면 됩니다.
하지만 복사하기 위해 알아야 하는 또 다른 함수가 있는데, 바로 GetWindowRect 함수입니다. 이유는 바로 계산기의 창 크기를 알기 위해서입니다. RECT 구조체는 left/right/top/bottom으로 구성되어 사각형으로 된 어떤 객체의 좌표를 담을 수 있는데, 이 구조체에 내가 원하는 윈도우의 크기를 얻어오는 함수가 GetWindowRect입니다. HWND 값을 이용하여 다음과 같이 사용할 수 있습니다.
RECT rectCalc;
GetWindowRect(hWndCalc, &rectCalc);
따라서 WM_PAINT 메시지 내에서 계산기를 캡쳐하여 화면에 그리기 위해서는 다음과 같이 구현할 수 있습니다.
HDC hDC;
PAINTSTRUCT ps;
HWND hWndCalc;
HDC hDCCalc;
RECT rectCalc;
(중략)
case WM_PAINT:
hDC = BeginPaint(hWnd, &ps);
hWndCalc = FindWindow(NULL, "계산기");
if(hWndCalc!=NULL) {
GetWindowRect(hWndCalc, &rectCalc);
hDCCalc = GetWindowDC(hWndCalc);
BitBlt(hDC, 0, 0, rectCalc.right, rectCalc.bottom, hDCCalc, 0, 0, SRCCOPY);
ReleaseDC(hWndCalc, hDCCalc);
}
EndPaint(hWnd, &ps);
break;
결과는 다음과 같습니다.
만약 FindWindow 대신 GetDesktopWindow() 라는 인자 없는 함수를 호출하여 그 리턴값을 똑같이 동작시킨다면, 이는 바탕 화면 전체를 캡쳐하는 것입니다. 즉 이는 다음과 같이 구현됩니다.
case WM_PAINT:
hDC = BeginPaint(hWnd, &ps);
hWndDesktop = GetDesktopWindow();
if(hWndDesktop!=NULL) {
GetWindowRect(hWndDesktop, &rectDesktop);
hDCDesktop = GetWindowDC(hWndDesktop);
BitBlt(hDC, 0, 0, rectDesktop.right, rectDesktop.bottom, hDCDesktop, 0, 0, SRCCOPY);
ReleaseDC(hWndDesktop, hDCDesktop);
}
EndPaint(hWnd, &ps);
break;
참고로 여기서 얻은 다른 윈도우의 DC를 이용해 다른 윈도우에 그림을 그리는 행동도 가능합니다. 즉 위의 hDCCalc 변수를 이용해 GetWindowDC와 ReleaseDC 사이에서 다음과 같이 호출해준다면 계산기 위에 동그라미가 그려지게(!) 됩니다. 물론 다음 화면에서 알 수 있듯이 타이틀바 및 경계선 영역에는 그려지지 않습니다.
Ellipse(hDCCalc, 10, 10, 300, 300);
2. 두 번째 방법 : PrintWindow
다른 윈도우의 DC를 얻어올 필요 없이, 단 하나의 함수 호출만으로 윈도우의 내용을 그대로 DC에 그릴 수 있습니다. 바로 PrintWindow라고 하는 함수입니다. PrintWindow 함수의 원형은 다음과 같습니다.
BOOL PrintWindow(
HWND hwnd,
HDC hdcBlt,
UINT nFlags
);
hwnd는 핸들, hdcBlt는 그릴 대상 DC, 즉 BeginPaint나 GetDC로 얻은 핸들을 말하며, 세 번째 인자는 NULL 또는 PW_CLIENTONLY를 넘길 수 있는데 PW_CLIENTONLY를 선택하면 클라이언트 영역만 그려지게 됩니다. 또한 이 함수를 이용하면 크기를 얻어오거나 하는 작업 없이 손쉽게 창을 얻어올 수 있다는 장점이 있습니다.
PrintWindow를 쓰는 경우 위의 코드가 단 몇 줄로 끝나게 됩니다.
hWndCalc = FindWindow(NULL, "계산기");
if(hWndCalc!=NULL) {
PrintWindow(hWndCalc, hDC, 0);
}
즉 위와 같은 동작이 이 함수 하나로 끝납니다.
만약 원하는 좌표에 캡쳐하기 위해서는 Bitmap을 저장하기 위한 MemDC를 만들고 여기를 타겟으로 하여 PrintWindow 함수를 호출합니다.
PrintWindow 함수는 데스크톱 화면(GetDesktopWindow로 얻은 핸들)에 대해서는 캡쳐되지 않습니다.
PrintWindow 함수가 간편한 만큼 몇 가지 장단점이 존재합니다.
첫째, PrintWindow 역시 DC에서 가져오는 원리는 비슷하나, 내부적으로 저장되어 있는 메모리 내용을 가져오기 때문에 화면에 가려진 영역이 있어도 정상적으로 출력됩니다. 이는 DC를 이용한 방법이 화면에 보이지 않는 영역을 정상적으로 출력하지 못하는 것과 대비됩니다. 화면에 보이지 않는다 하더라도 단지 다른 창에 가려진 경우에는 정상적으로 보이지만, 화면 가장자리로 가져가서 숨기거나 한 경우 DC에서 가져오는 방식으로는 정상적으로 출력되지 않습니다.
둘째, PrintWindow 함수는 다소 느린 단점이 있습니다. 예를 들어 동영상을 재생하는 창을 가져와 타이머를 이용하여 수시로 캡쳐하는 경우, DC를 이용하는 경우에는 잘 출력되지만, PrintWindow 함수를 이용하면 시스템 자원을 상당히 많이 사용하며 또한 원본 창에 무리를 주어 원본 창을 드래그할 수 없는 경우가 발생하기도 합니다.
3. 캡쳐된 이미지와 실제 창 모양이 왜 다를까?
다음 두 화면을 보면 캡쳐된 그림과 실제 화면이 다른 것을 알 수 있습니다. 실질적인 창 내용은 같지만, 테마가 적용되어 있는지 여부만 서로 다릅니다. 이 차이가 발생하는 이유는, 바로 여기서 하는 캡쳐는 OS상에서 자체적으로 저장한 메모리 장치 컨텍스트의 이미지를 가져오는 것이지, 엄밀히 말해 화면에 보여지는 실제 창 모양을 캡쳐하는 것이 아니기 때문에, Windows의 자체 효과가 지정된 화면이 출력되지 않는 것입니다.
비스타 이상에서 작업 표시줄에 마우스를 가져다 댔을 때 나오는 작은 이미지에 타이틀바가 나오지 않도록 되어 있는 이유도, 효과가 적용된 채 캡쳐되지 않기 때문에, 일부러 클라이언트 영역만 보이도록 저장된 것입니다.
하지만 GetDesktopWindow 함수를 이용하여 화면 전체를 캡쳐하는 경우에는 효과가 적용된 채로 출력됩니다.
따라서 만약 구현 후 캡쳐된 화면이 이질적이라서 거슬린다면, Windows 효과(Aero 등)를 프로그램 실행시에 끄도록 하는 방법을 통해 캡쳐화면과 실제화면을 일치시킬 수 있습니다.
4. 최소화된 창은 어떻게 캡쳐할까?
기본적으로는 최소화된 창은 캡쳐할 수 없습니다. 하지만 다음과 같이, 최소화 애니메이션을 없애고 윈도우를 임시로 투명하게 처리한 뒤, 캡쳐하는 순간에만 창을 원래 크기로 돌리는 방법이 있습니다. 다음 게시물 및 소스를 참조하면 자세하게 확인하실 수 있습니다.
http://www.codeproject.com/Articles/20651/Capturing-Minimized-Window-A-Kid-s-Trick
'IT 놀이터 > Elite Member Tech & Talk' 카테고리의 다른 글
[4기 대구 하호성] nullptr (0) | 2013.12.05 |
---|---|
[4기 대구 하호성] Unity 3D 안드로이드 플러그인 (1) | 2013.12.05 |
[4기 강북 송용길] Unit test with JUnit(5) (1) | 2013.12.05 |
[4기 신촌 김형진] 다른 윈도우의 핸들을 얻고 제어하기 (0) | 2013.12.05 |
[4기 신촌 박영웅] Android Application Stealth Update (3) (0) | 2013.12.05 |