Microsoft MVP성태의 닷넷 이야기
VC++: 121. DXGI를 이용한 윈도우 화면 캡처 소스 코드(Visual C++) [링크 복사], [링크+제목 복사],
조회: 30972
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
(연관된 글이 2개 있습니다.)
(시리즈 글이 10개 있습니다.)
VC++: 121. DXGI를 이용한 윈도우 화면 캡처 소스 코드(Visual C++)
; https://www.sysnet.pe.kr/2/0/11385

.NET Framework: 705. C# - SharpDX + DXGI를 이용한 윈도우 화면 캡처 소스 코드
; https://www.sysnet.pe.kr/2/0/11400

.NET Framework: 706. C# - SharpDX + DXGI를 이용한 윈도우 화면 캡처 소스 코드 + Direct2D 출력
; https://www.sysnet.pe.kr/2/0/11401

.NET Framework: 712. C# - SharpDX + DXGI를 이용한 윈도우 화면 캡처 소스 코드 + Direct2D 출력 + OpenCV
; https://www.sysnet.pe.kr/2/0/11407

.NET Framework: 713. C# - SharpDX + DXGI를 이용한 윈도우 화면 캡처 소스 코드 + Direct2D 출력 + OpenCV (2)
; https://www.sysnet.pe.kr/2/0/11408

.NET Framework: 913. C# - SharpDX + DXGI를 이용한 윈도우 화면 캡처 라이브러리
; https://www.sysnet.pe.kr/2/0/12238

.NET Framework: 1123. C# - (SharpDX + DXGI) 화면 캡처한 이미지를 빠르게 JPG로 변환하는 방법
; https://www.sysnet.pe.kr/2/0/12889

.NET Framework: 1126. C# - snagit처럼 화면 캡처를 연속으로 수행해 동영상 제작
; https://www.sysnet.pe.kr/2/0/12895

.NET Framework: 1128. C# - 화면 캡처한 이미지를 ffmpeg(FFmpeg.AutoGen)로 동영상 처리
; https://www.sysnet.pe.kr/2/0/12897

.NET Framework: 1152. C# - 화면 캡처한 이미지를 ffmpeg(FFmpeg.AutoGen)로 동영상 처리 (저해상도 현상 해결)
; https://www.sysnet.pe.kr/2/0/12963




DXGI를 이용한 윈도우 화면 캡처 소스 코드(Visual C++)

화면 캡처 글을 검색해 보면 다양한 글이 나옵니다.

Various methods for capturing the screen
; http://www.codeproject.com/Articles/5051/Various-methods-for-capturing-the-screen

일단 위의 글에서는 3가지 방법을 소개하는데 그중 DirectX 예제만 따라 해 보았지만 Windows 10에서 동작하지 않았습니다. 왜냐하면 GetFrontBufferData 호출에서,

hr = g_pd3dDevice->GetFrontBufferData(0, pSurface);
// hr == 0x8876086c (D3DERR_INVALIDCALL)

D3DERR_INVALIDCALL 오류가 발생하는데 이유를 모르겠습니다. (혹시 아시는 분 덧글 부탁드립니다.)

게다가 d3dx9tex.h 헤더 파일 및 d3dx9.lib 파일이 없다고 오류가 발생하는데 이를 정상적으로 컴파일하기 위해서는 다음의 과정을 거쳐야 합니다.

DXSDK_Jun10.exe 설치 시 "Error Code: S1023" 오류 해결하는 방법
; https://www.sysnet.pe.kr/2/0/1195

귀찮으니 ^^; 다음 캡처 방식으로 넘어가 보겠습니다. Windows 8부터 지원하는 DXGI를 이용하면,

DXGI fast screen capture 
; http://www.pavelgurenko.com/2013/12/dxgi-outputs-enumeration-and-fast.html

The second way of performing capture is DirectX Graphics Infrastructure - you command video card to store the whole screen or one/several monitors's contents inside separate part of its memory (surface).


속도는 물론이고 일반적인 GDI API로는 할 수 없는 DirectX 게임 화면까지도 안정적으로 캡처할 수 있다고 합니다. 친절하게도 위의 블로그를 쓴 사람이 github에 소스 코드도 공개한 것이 있고,

pgurenko/DXGICaptureSample 
; https://github.com/pgurenko/DXGICaptureSample

또한 다음과 같은 식으로 사용하면, 3840 * 2160 해상도에서 30 fps로 캡처할 수 있었다고 합니다.

// https://github.com/pgurenko/DXGICaptureSample/issues/2

#include "stdafx.h"
#include "DXGIManager.h"
#include <time.h>

DXGIManager g_DXGIManager;

int capture(RECT& rcDim, vector<BYTE>& buf, CComPtr<IWICImagingFactory>& spWICFactory) {
  DWORD dwWidth = rcDim.right - rcDim.left;
  DWORD dwHeight = rcDim.bottom - rcDim.top;
  DWORD dwBufSize = buf.size();

  HRESULT hr = g_DXGIManager.GetOutputBits(buf.data(), rcDim);
  if (FAILED(hr))
  {
    printf("GetOutputBits failed with hr=0x%08x\n", hr);
    return hr;
  }
  return 0;
}

int _tmain(int argc, _TCHAR* argv[]) {
  CoInitialize(NULL);

  g_DXGIManager.SetCaptureSource(CSDesktop);

  RECT rcDim;
  g_DXGIManager.GetOutputRect(rcDim);

  DWORD dwWidth = rcDim.right - rcDim.left;
  DWORD dwHeight = rcDim.bottom - rcDim.top;

  printf("dwWidth=%d dwHeight=%d\n", dwWidth, dwHeight);

  DWORD dwBufSize = dwWidth*dwHeight * 4;

  vector<BYTE> buf(dwBufSize);

  CComPtr<IWICImagingFactory> spWICFactory = NULL;
  HRESULT hr = spWICFactory.CoCreateInstance(CLSID_WICImagingFactory);
  if (FAILED(hr))
    return hr;

  clock_t t1 = clock();
  int i;
  int iterations = 100;  
  for (i = 0; i < iterations; i++) {
    capture(rcDim, buf, spWICFactory);
  }
  clock_t t2 = clock();
  printf("%d iterations: %0.0f fps\n", iterations, iterations / ((double)(t2 - t1) / CLOCKS_PER_SEC));

  return 0;
}

하지만 실제로 해보면 iterations 중에 capture 함수 내의 "GetOutputBits failed with hr=0x887a0027" 호출 실패가 있기 때문에 이를 감안하면 성능이 더 낮을 것입니다. 그래도 이 정도면 훌륭하죠. ^^


여기서 한 가지 중요한 점은, GetOutputBits 함수 내에서 호출하는 IDXGIOutputDuplication::AcquireNextFrame의 동작 방식입니다.

AcquireNextFrame은, 캡처 대상이 되는 화면에서 변화가 있어야 그것을 이미지 데이터로 반환합니다. 만약 지정한 시간 내에 변화가 없으면 DXGI_ERROR_WAIT_TIMEOUT 값을 반환합니다. 상당히 효율적이죠? ^^

실제로, 30fps 동영상을 바탕화면에서 재생하면서 IDXGIOutputDuplication::AcquireNextFrame을 호출하면 1초에 30번 정도 호출이 됩니다. 참고로, 바탕화면의 변화에는 (실제 캡처된 이미지에는 나오지 않지만) 마우스 커서 움직임도 포함이 됩니다.

DXGIOutputDuplication::AcquireNextFrame 내의 AcquireNextFrame 호출을 좀 더 살펴볼까요?

HRESULT DXGIOutputDuplication::AcquireNextFrame(IDXGISurface1** pDXGISurface, DXGIPointerInfo*& pDXGIPointer)
{
    DXGI_OUTDUPL_FRAME_INFO fi;
    CComPtr<IDXGIResource> spDXGIResource;

    HRESULT hr = m_DXGIOutputDuplication->AcquireNextFrame(50, &fi, &spDXGIResource);
    if(FAILED(hr))
    {
        __L_INFO("m_DXGIOutputDuplication->AcquireNextFrame failed with hr=0x%08x", hr);
        return hr;
    }

    // ... [생략] ...
}

인자로 전달한 50은 50ms 시간 내에서 다음 화면 데이터를 얻어내라는 것입니다. 만약 그 시간 내에 데이터를 얻을 수 없었다면 hr == 0x887a0027 (The timeout value has elapsed and the resource is not yet available) 오류가 발생합니다. 따라서 저 시간 값을 너무 낮추면 (변화가 없는 화면인 경우) 호출에 대한 오류가 쓸데없이 많이 발생할 수 있습니다.

그 외에, 모니터 3대인 경우에는 CSMonitor3 스위치 문을 다음과 같이 DXGIManager::GetOutputDuplication 함수에 추가하면 됩니다.

case CSMonitor3:
{
    int secondary = 0;
    // Return the first with !IsPrimary
    for (vector<DXGIOutputDuplication>::iterator iter = m_vOutputs.begin();
        iter != m_vOutputs.end();
        iter++)
    {
        DXGIOutputDuplication& out = *iter;
        if (!out.IsPrimary())
        {
            if (secondary == 0)
            {
                secondary = 1;
                continue;
            }

            outputs.push_back(out);
            break;
        }
    }
}
break;




위의 소개 글에서 pgurenko/DXGICaptureSample로 공개된 캡처 예제를 버퍼 그대로 Bitmap으로 바꿔 화면에 출력하는 코드를 다음과 같이 작성해 봤습니다.

#include "stdafx.h"
#include "DXGIManager.h"
#include <time.h>

DXGIManager g_DXGIManager;

LRESULT WINAPI MsgProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);

WNDCLASSEX g_wc = { sizeof(WNDCLASSEX), CS_CLASSDC, MsgProc, 0L, 0L, GetModuleHandle(NULL), NULL, NULL, NULL, NULL, L"Foo", NULL };
HWND g_hWnd;
HBITMAP g_hbm;
int hDC;
int g_screenX, g_screenY;

int _tmain(int argc, _TCHAR* argv[])
{
    CoInitialize(NULL);

    g_DXGIManager.SetCaptureSource(CaptureSource::CSMonitor1);

    RECT rcDim;
    g_DXGIManager.GetOutputRect(rcDim);

    DWORD dwWidth = rcDim.right - rcDim.left;
    DWORD dwHeight = rcDim.bottom - rcDim.top;

    g_screenX = dwWidth;
    g_screenY = dwHeight;

    RegisterClassEx(&g_wc);
    g_hWnd = CreateWindow(L"Foo", L"Foo", WS_OVERLAPPEDWINDOW, 0, 0, dwWidth, dwHeight, NULL, NULL, g_wc.hInstance, NULL);
    ::ShowWindow(g_hWnd, SW_SHOW);

    printf("dwWidth=%d dwHeight=%d\n", dwWidth, dwHeight);

    HDC hdcWin = GetDC(g_hWnd);
    RECT rc = { 0, 0, dwWidth, dwHeight };
    BITMAPINFO bmi = { 0 };
    bmi.bmiHeader.biSize = sizeof(bmi.bmiHeader);
    bmi.bmiHeader.biWidth = dwWidth;
    bmi.bmiHeader.biHeight = -dwHeight;
    bmi.bmiHeader.biPlanes = 1;
    bmi.bmiHeader.biBitCount = 32;
    bmi.bmiHeader.biCompression = BI_RGB;
    RGBQUAD *prgbBits;
    g_hbm = CreateDIBSection(hdcWin, &bmi, DIB_RGB_COLORS, &reinterpret_cast<void*&>(prgbBits), NULL, 0);

    DWORD dwBufSize = dwWidth*dwHeight * 4;

    vector<BYTE> buf(dwBufSize);
    BYTE *pBuf = buf.data();

    MSG msg;

    while (1)
    {
        if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE) == TRUE)
        {
            if (msg.message == WM_QUIT)
            {
                break;
            }

            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
        else
        {
            HRESULT hr = g_DXGIManager.GetOutputBits(pBuf, rcDim);

            if (hr != S_OK)
            {
                continue;
            }

            memcpy(prgbBits, pBuf, dwWidth * dwHeight * 4);
            ::InvalidateRect(g_hWnd, nullptr, TRUE);
        }
    }

    CoUninitialize();

    return msg.wParam;
}

LRESULT WINAPI MsgProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    if (msg == WM_CREATE)
    {
        return 0;
    }

    if (msg == WM_PAINT)
    {
        PAINTSTRUCT paint;
        HDC hDC = ::BeginPaint(hWnd, &paint);

        HDC hdcMem = CreateCompatibleDC(hDC);
        HBITMAP hbmPrev = (HBITMAP)::SelectObject(hdcMem, g_hbm);

        BitBlt(hDC, 0, 0, g_screenX, g_screenY, hdcMem, 0, 0, SRCCOPY);
        
        SelectObject(hdcMem, hbmPrev);
        DeleteDC(hdcMem);

        ::EndPaint(hWnd, &paint);
        return 0;
    }

    return DefWindowProc(hWnd, msg, wParam, lParam);
}

첨부 파일은 위의 예제 코드를 포함합니다.

실제로 실행해서 프로그램을 돌려보면... 부드러운 캡처 성능에 반하실 것입니다. ^^




시간 있으시면 다음의 글도 보시면 좋겠지요. ^^

Desktop Screen Capture on Windows via Windows Desktop Duplication API with Drawing of Cursor's Image
; https://www.codeproject.com/Tips/1116253/Desktop-Screen-Capture-on-Windows-via-Windows-Desk

DXGI desktop duplication sample
; https://code.msdn.microsoft.com/windowsdesktop/Desktop-Duplication-Sample-da4c696a

The reboot of Coding4Fun
; https://blogs.windows.com/windowsdeveloper/2015/12/21/the-reboot-of-coding4fun/




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]

[연관 글]






[최초 등록일: ]
[최종 수정일: 5/10/2023]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 



2018-06-17 05시05분
[지팡이] 잘 읽고갑니당
[guest]
2019-03-27 04시08분
[여우] DXGIManager에서 형변환 오류가 뜨는데 어떻게 해야하나요..?
2017 사용중입니다
[guest]
2019-03-27 09시27분
DXGIManager의 어느 코드에서 형 변환 오류가 뜬다는 거죠? 그리고 이 글에 첨부한 예제 코드로 실행했을 때 발생하는 건가요?
정성태
2022-03-22 11시51분
[홍길동] 비쥬얼스튜디오 2022 버전에서 소스를 복사하여 실행하면
DXGIManager.cpp에서
CComQIPtr<ID3D11Texture2D> spTextureResource = spDXGIResource;
이 부분이 에러가 납니다.

심각도    코드    설명    프로젝트    파일    줄    비표시 오류(Suppression) 상태
오류(활성)    E0312    "ATL::CComPtr<IDXGIResource>"에서 "ATL::CComQIPtr<ID3D11Texture2D, &__uuidof(ID3D11Texture2D)>"(으)로의 사용자 정의 변환이 적절하지 않습니다.    DXGICaptureSample    D:\[1]개발자파일\소스파일\기타\C++\샘플\DXGICaptureSample\DXGICaptureSample\DXGICaptureSample\DXGIManager.cpp    74
[guest]
2022-03-22 07시09분
@홍길동 "실행하면" 에러가 나는 것이 아니고 "컴파일하면" 에러가 나는 것이죠?

만약 오류 메시지를 올려 주시지 않았다면 ^^; 안 믿었을 것 같습니다. 위에서 @여우 님이란 분도 비슷한 오류를 경험한 듯한데, 그런 걸로 봐서는 분명히 컴파일 오류가 발생하고 있는 것 같지만... 개인적으로는 잘 이해가 안 되는 오류입니다. 왜냐하면, 해당 코드는 QueryInterface의 특성상 상속 구조를 밝힐 수 있는 것이 아니므로 형변환 관련한 체크가 관여할 수 없기 때문입니다. C++ 컴파일러가 저 변환이 적절하지 않다는 것을 도대체 무슨 근거로 판단한 것인지... ^^; 이해가 안 됩니다.

참고로, 제 컴퓨터에서는 (저 당시에도 컴파일이 잘 됐고) 현재 Visual Studio 2022에서 정상적으로 컴파일이 잘 됩니다. (물론 실행도 잘 됩니다.)

일단, 저 코드에서 오류가 발생한다면, 다음과 같이 코드를 바꿔보세요.

-----------------------------------------------------------------------
// 아래의 주석 코드와 그다음 QueryInterface를 호출하는 코드는 같은 역할을 합니다.

// CComQIPtr<ID3D11Texture2D> spTextureResource = spDXGIResource;
    
ID3D11Texture2D* pTextureResource;
hr = spDXGIResource->QueryInterface(__uuidof(ID3D11Texture2D), (LPVOID *)&pTextureResource);
if (hr == S_OK)
{
    printf("spDXGIResource 0x%p", pTextureResource);
    pTextureResource->Release();
}
-----------------------------------------------------------------------

CComQIPtr을 풀어 쓰면 위와 같이 QueryInterface로 처리하는 것과 같습니다. 암튼... 역시나 이해가 안 되는군요. C++ 컴파일러가 저 아래의 코드와 같이 수행하는 것을 변환이 적절하지 않다고 판단하는 건데... ^^;
정성태
2022-03-23 01시07분
[홍길동] 답변 감사합니다.
첨부파일을 받아 컴파일하면 에러가 나지않습니다.
2022버전에서 프로젝트를 새로 만든 다음 소스를 옮겨 컴파일하면 총 4곳이 에러가 납니다.

CComQIPtr<ID3D11Texture2D> spTextureResource = spDXGIResource;
CComQIPtr<IDXGISurface1> spDXGISurface = spD3D11Texture2D;
CComQIPtr<IDXGIDevice1> spDXGIDevice = spD3D11Device;
CComQIPtr<IDXGIDevice1> spDXGIDevice = spD3D11Device;

에러 내용은 같습니다. 원인이 궁금한데 이유를 모르겠습니다. ㅠㅠ
[guest]
2022-03-23 01시54분
@홍길동 그렇게 만든 프로젝트를 첨부 파일로 올려주세요. 질문/답변 게시판에서 올리시면 됩니다.

업데이트) 아래의 글에서 컴파일 에러 현상에 대해 설명했고, 현재 변경된 소스 코드로 첨부 파일을 업데이트했습니다.

CComPtr/CComQIPtr과 Conformance mode 옵션의 충돌
; https://www.sysnet.pe.kr/2/0/13014
정성태
2022-03-31 08시30분
[소울코더] 안녕하세요. 좋을 정보 감사합니다.

혹시
Window Handle 혹은 DC로 Window 하나만 타켓팅하여 DXGI로 캡쳐할 수 있는 방법이 있을까요?
[guest]
2022-03-31 10시05분
@소울코더 일단 DXGI는 특정 window만을 타게팅하여 캡처할 수는 없습니다. 윈도우 캡처를 위해서는 고전적인 BitBlt를 사용해야 합니다. 단지 UWP 응용 프로그램을 만든다면 Windows 10 1803 버전부터 가능한 Windows.Graphics.Capture 방법을 사용할 수 있습니다. 아래는 관련 문서입니다.

Screen capture
; https://learn.microsoft.com/en-us/windows/uwp/audio-video-camera/screen-capture

이후 Windows 10 1903부터는 Win32 응용 프로그램에서도 Windows.Graphics.Capture 방법을 사용할 수 있게 지원을 하는데, 이와 관련해서는 다음의 예제가 제공되고 있습니다.

Windows.UI.Composition-Win32-Samples/cpp/ScreenCaptureforHWND/
; https://github.com/microsoft/Windows.UI.Composition-Win32-Samples/tree/master/cpp/ScreenCaptureforHWND

유명한 OBS Studio 같은 프로그램도 윈도우 버전에 따라 저 방식을 이용해 윈도우 캡처를 지원합니다.

(언제 저도 시간나면 이 글의 예제에 윈도우 캡처 예제도 추가해야겠군요. ^^)
정성태
2022-05-07 08시07분
[곰팡이] 이코드에서 파일을 이미지로 저장하거나, 리소스로 사용하고싶은데 어떻게하면되나요
[guest]
2022-05-07 01시39분
@곰팡이 소스 코드를 보면, 캡처 화면을 HBITMAP으로 저장하고 있습니다. 따라서, 그것을 이용해 BMP 파일과 같은 형식으로 저장하시면 될 것입니다. 관련 소스 코드는 아래의 글에 있는 CreateBitmapInfoStruct 함수를 참고하세요.

Save HBITMAP to *.bmp file using only Win32
; https://stackoverflow.com/questions/24720451/save-hbitmap-to-bmp-file-using-only-win32
정성태
2022-05-07 07시49분
[곰팡이] #include "stdafx.h"
#include "DXGIManager.h"
#include <time.h>
#include <iostream>
#include <vector>
DXGIManager g_DXGIManager;

#include <stdio.h>
#include <crtdbg.h>
#include <Windows.h>
#include <fstream>
#if _DEBUG
#define new new(_NORMAL_BLOCK, __FILE__, __LINE__)
#define malloc(s) _malloc_dbg(s, _NORMAL_BLOCK, __FILE__, __LINE__)
#endif

#define BIT_COUNT 24;

BOOL SaveHBITMAPToFile(HBITMAP hBitmap, LPCSTR lpszFileName)
{
    // output stream 선언
    ofstream stream;
    // stream open (바이너리 모드)
    stream.open(lpszFileName, ios::binary);
    // stream이 열리지 않으면 에러.
    if (!stream.is_open())
    {
        cout << "File open error!!" << endl;
        return FALSE;
    }
    // 저장할 bitmap 선언
    BITMAP bitmap;
    // hBitmap으로 bitmap을 가져온다.
    GetObject(hBitmap, sizeof(bitmap), (LPSTR)&bitmap);
    // Bitmap Header 정보 설정
    BITMAPINFOHEADER bi;
    bi.biSize = sizeof(BITMAPINFOHEADER);
    bi.biWidth = bitmap.bmWidth;
    bi.biHeight = bitmap.bmHeight;
    bi.biPlanes = 1;
    bi.biBitCount = BIT_COUNT;
    bi.biCompression = BI_RGB;
    bi.biSizeImage = 0;
    bi.biXPelsPerMeter = 0;
    bi.biYPelsPerMeter = 0;
    bi.biClrUsed = 0;
    bi.biClrImportant = 0;
    // 컬러 사이즈
    int PalSize = (bi.biBitCount == 24 ? 0 : 1 << bi.biBitCount) * sizeof(RGBQUAD);
    int Size = bi.biSize + PalSize + bi.biSizeImage;
    BITMAPFILEHEADER fh;
    fh.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + PalSize;
    fh.bfReserved1 = 0;
    fh.bfReserved2 = 0;
    fh.bfSize = Size + sizeof(BITMAPFILEHEADER);
    fh.bfType = 0x4d42;
    // 파일에 bitmap 해더 작성
    stream.write((LPSTR)&fh, sizeof(BITMAPFILEHEADER));
    // DC 취득
    HDC hDC = GetDC(NULL);
    // 메모리 할당 (bitmap header)
    BITMAPINFO* header = (BITMAPINFO*)malloc(bi.biSize + PalSize);
    header->bmiHeader = bi;
    // hBitmap으로 부터 해더를 가져온다.
    GetDIBits(hDC, hBitmap, 0, bitmap.bmHeight, NULL, header, DIB_RGB_COLORS);
    // 이미지 전체 사이즈를 취득한다.
    bi = header->bmiHeader;
    if (bi.biSizeImage == 0)
    {
        // 해더 사이즈 설정이 안되면 강제 계산 설정
        bi.biSizeImage = ((bitmap.bmWidth * bi.biBitCount + 31) & ~31) / 8 * bitmap.bmHeight;
    }
    // 이미지 영역 메모리 할당
    Size = bi.biSize + PalSize + bi.biSizeImage;
    void* body = malloc(header->bmiHeader.biSizeImage);
    // hBitmap의 데이터를 저장
    GetDIBits(hDC, hBitmap, 0, header->bmiHeader.biHeight, body, header, DIB_RGB_COLORS);
    // 데이터 작성
    stream.write((LPSTR)&header->bmiHeader, sizeof(BITMAPINFOHEADER));
    stream.write((LPSTR)body, Size);
    // DC 해제
    ReleaseDC(NULL, hDC);
    // stream 해제
    stream.close();
    // 메모리 해제
    delete header;
    delete body;
}

int capture(RECT& rcDim, vector<BYTE>& buf, CComPtr<IWICImagingFactory>& spWICFactory) {
    DWORD dwWidth = rcDim.right - rcDim.left;
    DWORD dwHeight = rcDim.bottom - rcDim.top;
    DWORD dwBufSize = buf.size();

    while (1) {
        HRESULT hr = g_DXGIManager.GetOutputBits(buf.data(), rcDim);
        if (!FAILED(hr))
        {
            printf("succ\n");
            return hr;
        }
        else {
            printf("GetOutputBits failed with hr=0x%08x\n", hr);
        }
    }
    return 0;
}

int _tmain(int argc, _TCHAR* argv[]) {
    CoInitialize(NULL);

    g_DXGIManager.SetCaptureSource(CSDesktop);

    RECT rcDim;
    g_DXGIManager.GetOutputRect(rcDim);

    DWORD dwWidth = rcDim.right - rcDim.left;
    DWORD dwHeight = rcDim.bottom - rcDim.top;

    printf("dwWidth=%d dwHeight=%d\n", dwWidth, dwHeight);

    DWORD dwBufSize = dwWidth * dwHeight * 4;

    vector<BYTE> buf(dwBufSize);

    int buf1[80] = {0,};

    CComPtr<IWICImagingFactory> spWICFactory = NULL;
    HRESULT hr = spWICFactory.CoCreateInstance(CLSID_WICImagingFactory);
    if (FAILED(hr))
        return hr;

    int t1 = clock();
    int i;
    int iterations = 1;
    for (i = 0; i < iterations; i++) {
        capture(rcDim, buf, spWICFactory);

        printf("%d" ,buf.begin());

        vector<BYTE>::iterator p= buf.begin();
    
        BITMAPINFO bmi = { 0 };
        bmi.bmiHeader.biSize = sizeof(bmi.bmiHeader);
        bmi.bmiHeader.biWidth = dwWidth;
        bmi.bmiHeader.biHeight = -dwHeight;
        bmi.bmiHeader.biPlanes = 1;
        bmi.bmiHeader.biBitCount = 32;
        bmi.bmiHeader.biCompression = BI_RGB;

 /* HWND m_hwnd= GetDesktopWindow();
        HDC hdc = GetDC(m_hwnd);*/
        HDC hdc = CreateCompatibleDC(NULL);
        HBITMAP bitmap1 = CreateDIBitmap(hdc, &bmi, CBM_INIT, buf.data(), NULL, DIB_RGB_COLORS);
        ///
        SaveHBITMAPToFile(bitmap1, "C:\\배포용\\img\\test.bmp");
    }

    int t2 = clock();
    printf(" %dms \n", (t2 - t1));

    return 0;
}


다음과같이 vector정보를 바로 비트맵으로 저장하는걸 구현하고싶은데
혹시 어디가 오류인지 봐주실수있나요?
[guest]
2022-05-07 07시53분
[곰팡이] CreateDIBitmap 함수를쓰는데
두번째인자 &bmi에서
BITMAPINFO* 형식의 인수가 constBITMPAINFOHEADER*의 형식과 맞지않다는데...

그리고 이방법이 유효할까요?
[guest]
2022-05-07 08시40분
결과를 내려고 조급하게 서두르지 마시고, 소스 코드를 찬찬히 뜯어보면서 각 라인들이 어떤 역할을 하는지 잘 이해해 보세요. 할 수 있습니다. ^^

BITMAPINFO와 BITMAPINFOHEADER도 각각의 구조체가 어떻게 정의되어 있는지를 보면 알 수 있습니다.
정성태
2022-05-10 08시59분
[곰팡이] 감사합니다!
근데 ctrl alt delete 누르거나 모니터 연결끊어졌다연결되면 검은화면출력되는데
프로그램다시 안껏다키고 다시 나오게 할 수있나요?
[guest]
2022-05-10 09시09분
저도 그 부분은 세세하게 테스트하지는 않았는데, 아마도 관련 DXGI 인터페이스를 새롭게 초기화를 해야 할 것입니다. (혹시나 좀 더 나은 방법을 발견하시게 되거든 공유 부탁드립니다. ^^)
정성태

... 136  137  138  139  140  141  142  143  144  145  146  147  148  149  [150]  ...
NoWriterDateCnt.TitleFile(s)
1303정성태6/26/201227387개발 환경 구성: 152. sysnet DB를 SQL Azure 데이터베이스로 마이그레이션
1302정성태6/25/201229362개발 환경 구성: 151. Azure 웹 사이트에 사용자 도메인 네임 연결하는 방법
1301정성태6/20/201225759오류 유형: 156. KB2667402 윈도우 업데이트 실패 및 마이크로소프트 Answers 웹 사이트 대응
1300정성태6/20/201231746.NET Framework: 329. C# - Rabin-Miller 소수 생성방법을 이용하여 RSACryptoServiceProvider의 개인키를 직접 채워보자 [1]파일 다운로드2
1299정성태6/18/201232863제니퍼 .NET: 21. 제니퍼 닷넷 - Ninject DI 프레임워크의 성능 분석 [2]파일 다운로드2
1298정성태6/14/201234398VS.NET IDE: 72. Visual Studio에서 pfx 파일로 서명한 경우, 암호는 어디에 저장될까? [2]
1297정성태6/12/201231037VC++: 63. 다른 프로세스에 환경 변수 설정하는 방법파일 다운로드1
1296정성태6/5/201227667.NET Framework: 328. 해당 DLL이 Managed인지 / Unmanaged인지 확인하는 방법 - 두 번째 이야기 [4]파일 다운로드1
1295정성태6/5/201225072.NET Framework: 327. RSAParameters와 System.Numerics.BigInteger 이야기파일 다운로드1
1294정성태5/27/201248513.NET Framework: 326. 유니코드와 한글 - 유니코드와 닷넷을 이용한 한글 처리 [7]파일 다운로드2
1293정성태5/24/201229769.NET Framework: 325. System.Drawing.Bitmap 데이터를 Parallel.For로 처리하는 방법 [2]파일 다운로드1
1292정성태5/24/201223747.NET Framework: 324. First-chance exception에 대해 조건에 따라 디버거가 멈추게 할 수는 없을까? [1]파일 다운로드1
1291정성태5/23/201230262VC++: 62. 배열 초기화를 위한 기계어 코드 확인 [2]
1290정성태5/18/201235074.NET Framework: 323. 관리자 권한이 필요한 작업을 COM+에 대행 [7]파일 다운로드1
1289정성태5/17/201239230.NET Framework: 322. regsvcs.exe로 어셈블리 등록 시 시스템 변경 사항 [5]파일 다운로드2
1288정성태5/17/201226454.NET Framework: 321. regasm.exe로 어셈블리 등록 시 시스템 변경 사항 (3) - Type Library파일 다운로드1
1287정성태5/17/201229284.NET Framework: 320. regasm.exe로 어셈블리 등록 시 시스템 변경 사항 (2) - .NET 4.0 + .NET 2.0 [2]
1286정성태5/17/201238205.NET Framework: 319. regasm.exe로 어셈블리 등록 시 시스템 변경 사항 (1) - .NET 2.0 + x86/x64/AnyCPU [5]
1285정성태5/16/201233256.NET Framework: 318. gacutil.exe로 어셈블리 등록 시 시스템 변경 사항파일 다운로드1
1284정성태5/15/201225686오류 유형: 155. Windows Phone 연결 상태에서 DRIVER POWER STATE FAILURE 블루 스크린 뜨는 현상
1283정성태5/12/201233299.NET Framework: 317. C# 관점에서의 Observer 패턴 구현 [1]파일 다운로드1
1282정성태5/12/201226094Phone: 6. Windows Phone 7 Silverlight에서 Google Map 사용하는 방법 [3]파일 다운로드1
1281정성태5/9/201233176.NET Framework: 316. WPF/Silverlight의 그래픽 단위와 Anti-aliasing 처리를 이해하자 [1]파일 다운로드1
1280정성태5/9/201226149오류 유형: 154. Could not load type 'System.ServiceModel.Activation.HttpModule' from assembly 'System.ServiceModel, ...'.
1279정성태5/9/201224909.NET Framework: 315. 해당 DLL이 Managed인지 / Unmanaged인지 확인하는 방법 [1]파일 다운로드1
1278정성태5/8/201226139오류 유형: 153. Visual Studio 디버깅 - Unable to break execution. This process is not currently executing the type of code that you selected to debug.
... 136  137  138  139  140  141  142  143  144  145  146  147  148  149  [150]  ...