프로그램으로 화면을 캡쳐할 수 있는 방법은 참 많습니다. 하지만 초보 프로그래머들에게 코드로 작성해보라고 하면 어려워하는 것 같아서 초보자들이 쉽게 사용할 수 있는 방법을 소개하려고 합니다. 이 글에서는 CImage 클래스를 사용하여 화면을 캡쳐하는 방법에 대해서 소개할 것인데, 이 방법은 캡쳐된 화면을 원하는 이미지 파일(*.bmp, *.png, *.jpeg, ...)로 저장하기도 편하기 때문에 프로그래머들이 많이 사용합니다.
// 화면 전체를 캡쳐하기 위해서 Window DC 를 사용합니다. ::GetDC(NULL) 이라고 해도 결과는 동일합니다.
HDC h_dc = ::GetWindowDC(NULL);
// 캡쳐에 사용할 CImage 객체를 선언한다.
CImage tips_image;
// 수평 해상도를 얻는다.
int cx = ::GetSystemMetrics(SM_CXSCREEN);
// 수직 해상도를 얻는다.
int cy = ::GetSystemMetrics(SM_CYSCREEN);
// 화면의 색상 수를 얻는다.
int color_depth = ::GetDeviceCaps(h_dc, BITSPIXEL);
// 캡쳐한 이미지를 "test.png" 라는 이름으로 저장한다. tips_image.Save(L"test.png", Gdiplus::ImageFormatPNG);
// 화면 캡쳐에 사용했던 DC를 해제한다.
::ReleaseDC(NULL, h_dc);
tips_image.ReleaseDC();
이 코드를 수행하고 나면 작업경로에 "test.png" 라는 파일이 생길 것 입니다. Save 함수는 파일의 확장자로써 파일을 구성하는 것이 아니라 그 뒤에오는 Gdiplus::ImageFormatPNG 라는 인자를 가지고 이미지 파일 형식을 결정하게 됩니다. 따라서 Jpeg 와 같은 형식의 이미지로 저장하고 싶다면 아래와 같이 코드를 구성하시면 됩니다.
OpenGL로 구현한 프로그램에 화면 캡쳐 기능이 필요해서 오동님 블로그를 참조해서 사용하다가 리눅스(Qt)에서도 사용하게 될 일이 있어서 수정한 거 올립니다. 확인 결과 MFC, Win32 API 에서 문제 없이 잘 동작하며 리눅스에의 확인은 제 친구가 했는 데 리눅스에서도 문제 없이 잘 동작한다고 하네요.(비트맵 관련 구조체가 없어서 구조체는 직접 추가했다고 함)
하지만, 여기서 얻은 DC는 Client Area만의 DC입니다. 즉, 화면에 무엇인가를 그릴 수 있는 영역뿐, 타이틀바, 경계선, 메뉴바가 제외된 영역을 얻어오게 됩니다.
따라서, 윈도우의 DC를 얻기 위해서는 GetDCEx 함수나 GetWindowDC 함수를 이용해야 합니다. GetDCEx는 윈도우 외에도 DC를 얻는 여러 다양한 방법을 제시하고 있지만 사용이 복잡합니다. 하지만 GetWindowDC는 GetDC와 용법이 같아 다음과 같이 사용하기만 하면 됩니다.
바로 DC간에 고속으로 복사하는 BitBlt, StretchBlt 함수를 이용하면 됩니다. 이 중 StretchBlt 함수는 그림을 확대/축소하는 기능이 포함되어 있으므로, 일반적으로는 BitBlt 함수를 이용합니다.
또한 BitBlt, StretchBlt 함수는 비트 연산 기능이 포함되어 있지만, 단순 복사를 위해서는 마지막 인자인 Flag를 SRCCOPY로 넘겨 주면 됩니다.
하지만 복사하기 위해 알아야 하는 또 다른 함수가 있는데, 바로 GetWindowRect 함수입니다. 이유는 바로 계산기의 창 크기를 알기 위해서입니다. RECT 구조체는 left/right/top/bottom으로 구성되어 사각형으로 된 어떤 객체의 좌표를 담을 수 있는데, 이 구조체에 내가 원하는 윈도우의 크기를 얻어오는 함수가 GetWindowRect입니다. HWND 값을 이용하여 다음과 같이 사용할 수 있습니다.
RECT rectCalc; GetWindowRect(hWndCalc, &rectCalc);
따라서 WM_PAINT 메시지 내에서 계산기를 캡쳐하여 화면에 그리기 위해서는 다음과 같이 구현할 수 있습니다.
참고로 여기서 얻은 다른 윈도우의 DC를 이용해 다른 윈도우에 그림을 그리는 행동도 가능합니다. 즉 위의 hDCCalc 변수를 이용해 GetWindowDC와 ReleaseDC 사이에서 다음과 같이 호출해준다면 계산기 위에 동그라미가 그려지게(!) 됩니다. 물론 다음 화면에서 알 수 있듯이 타이틀바 및 경계선 영역에는 그려지지 않습니다.
Ellipse(hDCCalc, 10, 10, 300, 300);
반응형
728x90
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. 최소화된 창은 어떻게 캡쳐할까?
기본적으로는 최소화된 창은 캡쳐할 수 없습니다. 하지만 다음과 같이, 최소화 애니메이션을 없애고 윈도우를 임시로 투명하게 처리한 뒤, 캡쳐하는 순간에만 창을 원래 크기로 돌리는 방법이 있습니다. 다음 게시물 및 소스를 참조하면 자세하게 확인하실 수 있습니다.
1. Win32 api이용, Active Window의 handle을 찾음. [DllImport("user32.dll")] public static extern IntPtr GetForegroundWindow();
2. Win32 api이용, window handle값으로, 창 정보를 읽어옴. [DllImport("user32.dll")] public static extern IntPtr GetWindowRect(IntPtr hWnd, ref RECT rect);
3. screen에서 capture Graphics 의 CopyFromScreen 메서드 이용하여 Bitmap으로 capture Image or Bitmap 의 Save메서드를 이용하여, 파일로 저장 Bimap b = new Bitmap(rect.Width, rect.Height); Graphics g = Graphics.FromImage(b); g.CopyFromScreen(startPoint, Point.Empty, rect.Size); b.Save(sFile).... 1,2,3순으로 하면 간단히 캡쳐 저장됩니다. 글에 오타 있을테니 차근차근 해보세요. CopyFormScreen의 desctop전체 크기넣으면 전체화면 캡쳐가 됩니다.
=======================
=======================
=======================
출처: http://lesomnus.tistory.com/10
Intro
템 파밍을 위해 잠수를 하는데, 젠이 됐는지 안됐는지 계속 확인하는게 비효율적이었다. 화면을 주기적으로 캡처하고 몹이 인식되면 알림을 띄우기 위해 이러한 삽질을 하게 되었다. 사실 캡처 자체는 문제가 아니었는데, 캡처할 대상이 '비활성 window'에다가 게임은 DiretX로 렌더링 되는 화면이었고, 이는 캡처 시 검은 화면만을 표시한다.
Try#1
사실 WinAPI를 이용한 프로그래밍 조차 처음이었고, 당연히 구글링부터 시작했다. 일단 이 글을 참고했다. 프로그래머라면 일단 Copy&Paste가 기본이 아닌가.1
자, 빨간 밑줄을 모두 없애 주었고, 두근거리는 마음으로 컴파일하고 실행을 했지만 런타임 에러가 발생하였다. GetWindowRect라는 놈이 문제였는데, 이 함수의 용도는 window 크기 정보를 알아내는게 전부였다. 단순 구조체라고 생각했던 RECT가 생각만큼 단순한 녀석이 아니었나 보다. 크기 정보를 얻을 수 있는 다른 방법이 없는가 알아보던 중, Graphics 클래스 문서에서 HWND로 정보를 가져올 수 있는 메소드를 찾았다.
아주 좋다. Window 메뉴바도 함께 찍히는데, 필요한 정보가 아니므로,MSDN 문서에 의하면, 12번 줄의LParam값으로PW_CLIENTONLY주면 클라이언트 화면만 캡쳐한다. 현재는 정의된 값이 아니므로,0x1로 하였다.
하지만 StackOverflow글의 본문에도 적혀있듯이, DirectX 렌더링 화면은 캡쳐하지 못하는것을 확인하였다. 하지만 답글에 달린 댓글에 놀라운 정보가 있었다.
Solved
"it works with pretty much every window except for Direct X / WPF windows." - New flag may help. According to Dave Anderson's comment (msdn.microsoft.com/en-us/library/dd162869.aspx), the PW_RENDERFULLCONTENT flag on Win8.1+ works with "windows that use DirectX or DirectComposition".
링크를 따라가봤지만, PW_RENDERFULLCONTENT같은 플래그는 없었다. 혹시나 싶어 플래그로 0x2, 0x3을 넣어봤는데, DirectX 렌더링 화면이 매우 잘 캡쳐되었다!
해당플래그는 윈도우7에서는 작동안하는거지요?
윈도우 10에서 테스트 해봤는데 7은 잘모르겠군요...
아주 훌륭한 삽질이군여 이건 캡쳐를 했다가 나중에 써먹어야할듯
감사합니다. 캡쳐가 정말 되네요!!!!
감사합니다.
정말 좋은 정보네요
회사컴퓨터에 테스트 해봤는데 윈도우7은 안되더군요..아마 다이렉트 x 버전때문인듯 합니다.
저기 마지막 스택오버플로우 답변에 "...PW_RENDERFULLCONTENT flag on Win8.1+ works..."
라는걸 보니 윈도우 8이상만 되는것 같네요
현재 c#으로 가려진 윈도우 창을 캡처하고싶어 공부하고있는데
궁금한점이 있습니다.
WinAPIs.User32.PrintWindow(hwnd, hdcBitmap, 1);
WinAPIs.Gdi32.CreateRectRgn(0, 0, 0, 0);
이 코드에서 WinAPIs.User32 와 WinAPIs.Gdi32. 부분에서 정의가 포함되어 있지 않다고 오류가 납니다.
따로 선언해줄것이 필요한건지 혹은 추가해야하는 부분인지 궁금합니다.
감사합니다 ^__^