5

I made some research through the web, and found some useful code. I changed it a bit, in attempt to capture the whole screen and generate a buffer that I can send through udp packets:

#include <iostream>
#include <Windows.h>
#include <fstream>

void CapruteScreenAndSaveToFile()
{
    uint16_t BitsPerPixel = 24;
    uint32_t Width = GetSystemMetrics(SM_CXSCREEN);
    uint32_t Height = GetSystemMetrics(SM_CYSCREEN);

    // Create Header
    BITMAPFILEHEADER Header;
    memset(&Header, 0, sizeof(Header));
    Header.bfType = 0x4D42;
    Header.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);

    // Create Info
    BITMAPINFO Info;
    memset(&Info, 0, sizeof(Info));
    Info.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
    Info.bmiHeader.biWidth = Width;
    Info.bmiHeader.biHeight = Height;
    Info.bmiHeader.biPlanes = 1;
    Info.bmiHeader.biBitCount = BitsPerPixel;
    Info.bmiHeader.biCompression = BI_RGB;
    Info.bmiHeader.biSizeImage = Width * Height * (BitsPerPixel > 24 ? 4 : 3);

    // Capture screen and save to Pixels
    char* Pixels = NULL;
    HDC MemDC = CreateCompatibleDC(0);//Context);
    HBITMAP Section = CreateDIBSection(MemDC, &Info, DIB_RGB_COLORS, (void**)&Pixels, 0, 0);
    DeleteObject(SelectObject(MemDC, Section));
    BitBlt(MemDC, 0, 0, Width, Height, GetDC(0), 0, 0, SRCCOPY);
    DeleteDC(MemDC);

    // Concatenate everything
    char * buffer = (char*)malloc(sizeof(Header) + sizeof(Info.bmiHeader) + (((BitsPerPixel * Width + 31) & ~31) / 8) * Height);

    memcpy(buffer, (char*)&Header, sizeof(Header));
    memcpy(buffer + sizeof(Header), (char*)&Info.bmiHeader, sizeof(Info.bmiHeader));
    memcpy(buffer + sizeof(Header) + sizeof(Info.bmiHeader), Pixels, (((BitsPerPixel * Width + 31) & ~31) / 8) * Height);

    // Save to file
    std::fstream hFile("Foo.bmp", std::ios::out | std::ios::binary);

    hFile.write(buffer, sizeof(Header) + sizeof(Info.bmiHeader) + (((BitsPerPixel * Width + 31) & ~31) / 8) * Height);

    // Clean up
    hFile.close();
    DeleteObject(Section);
    free(buffer);
}


int main()
{
    CapruteScreenAndSaveToFile();

    return 0;
}

But it only seems to capture this part of my desktop:

enter image description here

And that's even though I use CreateCompatibleDC(0).

Barmak Shemirani
  • 30,904
  • 6
  • 40
  • 77
W2a
  • 736
  • 2
  • 9
  • 23
  • I tested your code on my computer. It is working fine. The "bmp" generated has full screen capture. – Pavan Chandaka Nov 01 '16 at 19:57
  • Don't forget that there is [SetWindowDisplayAffinity](https://msdn.microsoft.com/en-us/library/windows/desktop/dd375340.aspx), as well as OpenGL. Both can pose as an obstacle to your code, and make it fail. Not the issue you are trying to solve here, but something you'll inevitably run into. – IInspectable Nov 02 '16 at 11:43

1 Answers1

7

If the computer is on high DPI settings, and the application is not DPI aware, then the system will lie to the application and give the wrong screen size.

You will find the following code shows width and height which are smaller than the actual screen size:

uint32_t Width = GetSystemMetrics(SM_CXSCREEN);
uint32_t Height = GetSystemMetrics(SM_CYSCREEN);
std::cout << Width << " x " << Height << "\n";

The solution is to add DPI awareness to application.

To add DPI compatibility:

In Visual Studio 2015, go to Project Properties -> Manifest tools, set DPI awareness to "Per Monitor High DPI Aware" or "High DPI Aware"

If you are using some old compiler...

1) create a file "myapp.manifest" with this content:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
  <application xmlns="urn:schemas-microsoft-com:asm.v3">
    <windowsSettings>
      <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">True/PM</dpiAware>
    </windowsSettings>
  </application>
</assembly>

2) Add *.rc file to your project with this content:

1 24 "myapp.manifest"

3) Rebuild the project

Barmak Shemirani
  • 30,904
  • 6
  • 40
  • 77
  • This is probably the right answer, but it doesn't work for me in my Windows 8.1, which is obviously newer than Vista .. Hence I am still in a need of further help. It says SetProcessDPIAware was not declared in this scope, even though I included Windows.h and linked User32.lib . – W2a Nov 04 '16 at 14:02
  • `SetProcessDPIAware` doesn't work because you didn't define `_WIN32_WINNT` or you are using MinGW-32 which is not compatible. Anyway, the correct way to add DPI awareness is through the manifest file. Don't make this in to a puzzle where I have to guess everything, provide some minimum information so the errors you are getting are reproducible. – Barmak Shemirani Nov 05 '16 at 01:56
  • Yep, I'm using MinGW-32. What should I use then? And much thanks ! – W2a Nov 05 '16 at 06:23
  • I think you are reading only part of the answer and part of the comment. You have to use manifest file, as shown in answer. – Barmak Shemirani Nov 05 '16 at 06:50
  • Sorry, I didn't notice you edited the answer, but it doesn't work.. I created a dpi.manifest file in the project's folder with the contents you have provided, and added a "resource.rc" file with content 1 24 "dpi.manifest" but the compiler still doesnt recognize SetProcessDPIAware .. – W2a Nov 05 '16 at 07:10
  • Don't use `SetProcessDPIAware`. The program will report the correct width/height if manifest file is setup properly – Barmak Shemirani Nov 05 '16 at 07:18