Microsoft MVP성태의 닷넷 이야기
VC++: 121. DXGI를 이용한 윈도우 화면 캡처 소스 코드(Visual C++) [링크 복사], [링크+제목 복사]
조회: 22125
글쓴 사람
정성태 (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 인터페이스를 새롭게 초기화를 해야 할 것입니다. (혹시나 좀 더 나은 방법을 발견하시게 되거든 공유 부탁드립니다. ^^)
정성태

... 31  [32]  33  34  35  36  37  38  39  40  41  42  43  44  45  ...
NoWriterDateCnt.TitleFile(s)
12818정성태8/31/20217145Windows: 198. 윈도우 - 작업 관리자에서 (tensorflow 등으로 인한) GPU 연산 부하 보는 방법
12817정성태8/31/20219667스크립트: 25. 파이썬 - 윈도우 환경에서 directml을 이용한 tensorflow의 AMD GPU 사용 방법
12816정성태8/30/202115080스크립트: 24. 파이썬 - tensorflow 2.6 NVidia GPU 사용 방법 [2]
12815정성태8/30/20218207개발 환경 구성: 602. WSL 2 - docker-desktop-data, docker-desktop (%LOCALAPPDATA%\Docker\wsl\data\ext4.vhdx) 파일을 다른 디렉터리로 옮기는 방법
12814정성태8/30/202110514.NET Framework: 1110. C# 11 - 인터페이스 내에 정적 추상 메서드 정의 가능 (DIM for Static Members) [2]파일 다운로드1
12813정성태8/29/20218748.NET Framework: 1109. C# 10 - (11) Lambda 개선파일 다운로드1
12812정성태8/28/20218379.NET Framework: 1108. C# 10 - (10) 개선된 #line 지시자
12811정성태8/27/20218580Linux: 44. 윈도우 개발자를 위한 리눅스 fork 동작 방식 설명 (파이썬 코드)
12810정성태8/27/20217371.NET Framework: 1107. .NET Core/5+에서 동적 컴파일한 C# 코드를 (Breakpoint도 활용하며) 디버깅하는 방법 - #line 지시자파일 다운로드1
12809정성태8/26/20218025.NET Framework: 1106. .NET Core/5+에서 C# 코드를 동적으로 컴파일/사용하는 방법 [1]파일 다운로드1
12808정성태8/25/20219286오류 유형: 758. go: ...: missing go.sum entry; to add it: go mod download ...
12807정성태8/25/20219228.NET Framework: 1105. C# 10 - (9) 비동기 메서드가 사용할 AsyncMethodBuilder 선택 가능파일 다운로드1
12806정성태8/24/20216852개발 환경 구성: 601. PyCharm - 다중 프로세스 디버깅 방법
12805정성태8/24/20218088.NET Framework: 1104. C# 10 - (8) 분해 구문에서 기존 변수의 재사용 가능파일 다운로드1
12804정성태8/24/20218910.NET Framework: 1103. C# 10 - (7) Source Generator V2 APIs
12803정성태8/23/20218399개발 환경 구성: 600. pip cache 디렉터리 옮기는 방법
12802정성태8/23/20218772.NET Framework: 1102. .NET Conf Mini 21.08 - WinUI 3 따라해 보기 [1]
12801정성태8/23/20218256.NET Framework: 1101. C# 10 - (6) record class 타입의 ToString 메서드를 sealed 처리 허용파일 다운로드1
12800정성태8/22/20218479개발 환경 구성: 599. PyCharm - (반대로) 원격 프로세스가 PyCharm에 디버그 연결하는 방법
12799정성태8/22/20218594.NET Framework: 1100. C# 10 - (5) 속성 패턴의 개선파일 다운로드1
12798정성태8/21/20219865개발 환경 구성: 598. PyCharm - 원격 프로세스를 디버그하는 방법
12797정성태8/21/20217599Windows: 197. TCP의 MSS(Maximum Segment Size) 크기는 고정된 것일까요?
12796정성태8/21/20218284.NET Framework: 1099. C# 10 - (4) 상수 문자열에 포맷 식 사용 가능파일 다운로드1
12795정성태8/20/20218852.NET Framework: 1098. .NET 6에 포함된 신규 BCL API - 스레드 관련
12794정성태8/20/20218307스크립트: 23. 파이썬 - WSGI를 만족하는 최소한의 구현 코드 및 PyCharm에서의 디버깅 방법 [1]
12793정성태8/20/20219034.NET Framework: 1097. C# 10 - (3) 개선된 변수 초기화 판정파일 다운로드1
... 31  [32]  33  34  35  36  37  38  39  40  41  42  43  44  45  ...