I'm facing an issue when I want to get a screenshot compressed with RLE using windows api.
I'm using Qt/C++.
Here is my code :
void Screenshot::captureScreenCompressed() {
HWND hDesktopWnd = GetDesktopWindow();
HDC hDesktopDC = GetDC(hDesktopWnd);
HDC hCaptureDC = CreateCompatibleDC(hDesktopDC);
HBITMAP hCaptureBitmap = CreateCompatibleBitmap(hDesktopDC, _screenWidth, _screenHeight);
SelectObject(hCaptureDC, hCaptureBitmap);
BitBlt(hCaptureDC, 0, 0, _screenWidth, _screenHeight, hDesktopDC, 0,0, SRCCOPY | CAPTUREBLT);
BITMAPINFO bmi = {0};
bmi.bmiHeader.biSize = sizeof(bmi.bmiHeader);
bmi.bmiHeader.biWidth = _screenWidth;
bmi.bmiHeader.biHeight = - _screenHeight;
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biBitCount = 32;
bmi.bmiHeader.biCompression = BI_RLE4;
if (GetDIBits(hCaptureDC, hCaptureBitmap, 0, _screenHeight, NULL, &bmi, DIB_RGB_COLORS) == 0) {
qDebug() << "Problem on Compression";
}
ReleaseDC(hDesktopWnd, hDesktopDC);
DeleteDC(hCaptureDC);
DeleteObject(hCaptureBitmap);
}
The final sizeImage is 0 and the getDiBits function returns 0.
I need some help thanks !
Related
I'm using QT/C++ and I would like to capture the screen of my desktop and get the pixels buffer.
I'm using this function but it returns weird pixels values, can someone tell me why ?
void CaptureScreen()
{
int nScreenWidth = GetSystemMetrics(SM_CXSCREEN);
int nScreenHeight = GetSystemMetrics(SM_CYSCREEN);
HWND hDesktopWnd = GetDesktopWindow();
HDC hDesktopDC = GetDC(hDesktopWnd);
HDC hCaptureDC = CreateCompatibleDC(hDesktopDC);
HBITMAP hCaptureBitmap = CreateCompatibleBitmap(hDesktopDC, nScreenWidth, nScreenHeight);
SelectObject(hCaptureDC, hCaptureBitmap);
BitBlt(hCaptureDC, 0, 0, nScreenWidth, nScreenHeight, hDesktopDC, 0,0, SRCCOPY|CAPTUREBLT);
BITMAPINFO bmi = {0};
bmi.bmiHeader.biSize = sizeof(bmi.bmiHeader);
bmi.bmiHeader.biWidth = nScreenWidth;
bmi.bmiHeader.biHeight = nScreenHeight;
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biBitCount = 32;
bmi.bmiHeader.biCompression = BI_RGB;
unsigned char *pPixels = new unsigned char[nScreenWidth * nScreenHeight * 4];
GetDIBits(hCaptureDC, hCaptureBitmap, 0, nScreenHeight, pPixels, &bmi, DIB_RGB_COLORS);
for (int i = 0; i < 200; i++) {
qDebug() << (int) pPixels[i];
}
delete[] pPixels;
ReleaseDC(hDesktopWnd, hDesktopDC);
DeleteDC(hCaptureDC);
DeleteObject(hCaptureBitmap);
}
Output is 1 1 1 255 1 1 1 255 ... but first pixels on the top of screen I have is white. It should be 255 255 255 255 etc...
Thank you !
Resolved!
Thanks to RbMm comment :
A bottom-up DIB is specified by setting the height to a positive number, while a top-down DIB is specified by setting the height to a negative number.
the first line is bottom. if want another order use bmi.bmiHeader.biHeight = -nScreenHeight;
I just replaced this line bmi.bmiHeader.biHeight = nScreenHeight; by this bmi.bmiHeader.biHeight = - nScreenHeight; and I get pixels from top line.
It can help someone else :)
As an example, a IMediaSample is filled by random data to test a DirectShow Video Source Filter:
HRESULT CVCamStream::FillBuffer(IMediaSample *pms)
{
REFERENCE_TIME rtNow;
REFERENCE_TIME avgFrameTime = ((VIDEOINFOHEADER*)m_mt.pbFormat)->AvgTimePerFrame;
rtNow = m_rtLastTime;
m_rtLastTime += avgFrameTime;
pms->SetTime(&rtNow, &m_rtLastTime);
pms->SetSyncPoint(TRUE);
BYTE *pData;
long lDataLen;
pms->GetPointer(&pData);
lDataLen = pms->GetSize();
for(int i = 0; i < lDataLen; ++i) pData[i] = rand();
}
Instead, I want to fill the buffer with a bitmap loaded from file-system, and this is what I'm trying to do:
AM_MEDIA_TYPE *mt;
HRESULT hr;
hr = pms->GetMediaType(&mt);
if (FAILED(hr)) return hr;
VIDEOINFOHEADER *pVih;
if ((mt->formattype == FORMAT_VideoInfo) &&
(mt->cbFormat >= sizeof(VIDEOINFOHEADER)) &&
(mt->pbFormat != NULL))
{
pVih = (VIDEOINFOHEADER*)mt->pbFormat;
}
else
{
FreeMediaType(*mt);
return VFW_E_INVALIDMEDIATYPE;
}
DWORD dwBmpSize = ((640 * 3 + 31) / 32) * 4 * 480; // 640x480x24bit
HANDLE hDIB = GlobalAlloc(GHND, dwBmpSize);
char *lpbitmap = (char *)GlobalLock(hDIB);
HBITMAP hBMP = (HBITMAP)LoadImage(NULL, "path/to/my/image.png", IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
HDC hdc = GetDC(NULL);
GetDIBits(hdc, hBMP, 0, 480, lpbitmap, (BITMAPINFO*)&pVih->bmiHeader, DIB_RGB_COLORS);
SetDIBitsToDevice(
hdc, 0, 0,
pVih->bmiHeader.biWidth,
pVih->bmiHeader.biHeight,
0, 0,
0,
pVih->bmiHeader.biHeight,
lpbitmap,
(BITMAPINFO*)&pVih->bmiHeader,
DIB_RGB_COLORS
);
But when I try it with Skype is just crashes...
A part any hint about the code above, how I'm supposed to debug such a filter?
Here the working code to fill IMediaSample with a bitmap:
HRESULT CVCamStream::FillBuffer(IMediaSample *pms)
{
REFERENCE_TIME rtNow;
REFERENCE_TIME avgFrameTime = ((VIDEOINFOHEADER*)m_mt.pbFormat)->AvgTimePerFrame;
rtNow = m_rtLastTime;
m_rtLastTime += avgFrameTime;
pms->SetTime(&rtNow, &m_rtLastTime);
pms->SetSyncPoint(TRUE);
BYTE *pData;
long lDataLen;
pms->GetPointer(&pData);
lDataLen = pms->GetSize();
HDC hdc = GetDC(NULL);
HBITMAP hBmp = (HBITMAP)LoadImage(NULL, "path/to/image.bmp", IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
BITMAP bmp;
GetObject(hBmp, sizeof(BITMAP), &bmp);
BITMAPINFOHEADER bi;
bi.biSize = sizeof(BITMAPINFOHEADER);
bi.biWidth = bmp.bmWidth;
bi.biHeight = bmp.bmHeight;
bi.biPlanes = 1;
bi.biBitCount = 24;
bi.biCompression = BI_RGB;
bi.biSizeImage = 0;
bi.biXPelsPerMeter = 0;
bi.biYPelsPerMeter = 0;
bi.biClrUsed = 0;
bi.biClrImportant = 0;
DWORD dwBmpSize = ((bmp.bmWidth * bi.biBitCount + 31) / 32) * 4 * bmp.bmHeight;
HANDLE hDIB = GlobalAlloc(GHND, dwBmpSize);
char *lpbitmap = (char *)GlobalLock(hDIB);
GetDIBits(hdc, hBmp, 0, (UINT)bmp.bmHeight, lpbitmap, (BITMAPINFO *)&bi, DIB_RGB_COLORS);
memcpy(pData, lpbitmap, lDataLen);
return NOERROR;
} // FillBuffer
I'm making a C++ program which takes screenshot of the entire screen. I am facing quite a problem. When I run the program, it takes screenshot of the console screen only, not the entire desktop.
HDC Screen = CreateDC(L"DISPLAY", NULL, NULL, NULL);
HDC Capture = CreateCompatibleDC(Screen);
int width = GetDeviceCaps(Screen, HORZRES);
int height = GetDeviceCaps(Screen, VERTRES);
LPBYTE lpcapture;
BITMAPINFO bmiCapture =
{ { sizeof(BITMAPINFOHEADER),width,height,1,24,BI_RGB,0,0,0,0,0 } };
HBITMAP hbmCapture = CreateDIBSection(Screen, &bmiCapture, DIB_RGB_COLORS, (LPVOID *)&lpcapture, NULL, 0);
if (hbmCapture)
{
HBITMAP hbmOld = (HBITMAP) SelectObject(Capture, Capture);
BitBlt(Capture, 0, 0, width, height, Screen, 0, 0, SRCCOPY);
SelectObject(Capture, hbmOld);
}
DeleteDC(Capture);
DeleteDC(Screen);
return hbmCapture;
This should work in a console program, but it would show black console screen in the middle
int ScreenCapture(const char* fname)
{
int result = 0;
HWND hWnd = GetDesktopWindow();
HBITMAP hbmScreen = NULL;
HDC hdcScreen = GetDC(NULL);
HDC hdcWindow = GetDC(hWnd);
int w = GetSystemMetrics(SM_CXSCREEN);
int h = GetSystemMetrics(SM_CYSCREEN);
HDC hdcMemDC = CreateCompatibleDC(hdcWindow);
if (!hdcMemDC) goto cleanup;
hbmScreen = CreateCompatibleBitmap(hdcWindow, w, h);
if (!hbmScreen) goto cleanup;
SelectObject(hdcMemDC, hbmScreen);
if (!BitBlt(hdcMemDC, 0, 0, w, h, hdcWindow, 0, 0, SRCCOPY)) goto cleanup;
BITMAPFILEHEADER bmfHeader;
BITMAPINFOHEADER bi;
bi.biSize = sizeof(BITMAPINFOHEADER);
bi.biWidth = w;
bi.biHeight = h;
bi.biPlanes = 1;
bi.biBitCount = 32;
bi.biCompression = BI_RGB;
bi.biSizeImage = 0;
bi.biXPelsPerMeter = 0;
bi.biYPelsPerMeter = 0;
bi.biClrUsed = 0;
bi.biClrImportant = 0;
DWORD dwBmpSize = ((w * bi.biBitCount + 31) / 32) * 4 * h;
HANDLE hDIB = GlobalAlloc(GHND, dwBmpSize);
char* lpbitmap = (char*)GlobalLock(hDIB);
GetDIBits(hdcWindow, hbmScreen, 0, h, lpbitmap, (BITMAPINFO*)&bi, DIB_RGB_COLORS);
bmfHeader.bfOffBits = (DWORD)sizeof(BITMAPFILEHEADER) + (DWORD)sizeof(BITMAPINFOHEADER);
bmfHeader.bfSize = dwBmpSize + sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
bmfHeader.bfType = 0x4D42; //'BM' for Bitmaps
DWORD temp = 0;
HANDLE hFile = CreateFileA(fname, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
WriteFile(hFile, (LPSTR)&bmfHeader, sizeof(BITMAPFILEHEADER), &temp, NULL);
WriteFile(hFile, (LPSTR)&bi, sizeof(BITMAPINFOHEADER), &temp, NULL);
WriteFile(hFile, (LPSTR)lpbitmap, dwBmpSize, &temp, NULL);
CloseHandle(hFile);
GlobalUnlock(hDIB);
GlobalFree(hDIB);
result = 1; //success
cleanup:
DeleteObject(hbmScreen);
DeleteObject(hdcMemDC);
ReleaseDC(NULL, hdcScreen);
ReleaseDC(hWnd, hdcWindow);
return result;
}
I want to draw the text on Bitmap and I did it with the summary code below
BITMAPINFO bitmapInfo;
bitmapInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bitmapInfo.bmiHeader.biWidth = _imgWidth;
bitmapInfo.bmiHeader.biHeight = _imgHeight;
bitmapInfo.bmiHeader.biPlanes = 1;
bitmapInfo.bmiHeader.biBitCount = 24;
bitmapInfo.bmiHeader.biCompression = BI_RGB;
bitmapInfo.bmiHeader.biSizeImage = 0;
HDC hdc = GetDC(NULL);
if (hdc == NULL)
return false;
HFONT hFont = CreateFont( 50, 0, 0, 0, FW_BOLD, 0, 0, 0, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, 0, DEFAULT_QUALITY, DEFAULT_PITCH | FF_DONTCARE, "Arial" );
if(hFont == NULL)
return false;
HBITMAP hBitmap = CreateDIBitmap(hdc, (LPBITMAPINFOHEADER) &bitmapInfo.bmiHeader, CBM_INIT, _BRG24arrayIn, (LPBITMAPINFO) &bitmapInfo, DIB_RGB_COLORS);
if(hBitmap == NULL)
return false;
HDC hMemDC = CreateCompatibleDC(hdc);
if (hMemDC == NULL)
return false;
HBITMAP hBitmapOld = (HBITMAP)SelectObject(hMemDC, hBitmap);
if( hBitmapOld == NULL )
return false;
HFONT hFontOld = (HFONT)SelectObject(hMemDC, hFont);
if ( hFontOld == NULL )
return false;
SetBkMode(hMemDC, TRANSPARENT);
SetTextColor(hMemDC, 0x0000FF00);
RECT rect;
SetRect(&rect, 0, 0, _imgWidth, _imgHeight);
if (DrawText(hMemDC, "11:41:33", -1, &rect, DT_TOP|DT_LEFT) == 0)
return false;
GetDIBits(hdc, hBitmap, 0, _imgHeight, _BRG24arrayOut, (LPBITMAPINFO)&bitmapInfo, DIB_RGB_COLORS);
return true;
The text that I want to draw is "11:41:33" and text alignment is DT_TOP|DT_LEFT
But the result is the text is rotated and occurred on the LEFT-BOTTOM as result image below
The input array _BRG24arrayIn is in BRG24 format, someone can tell me what happen?
Many thanks,
T&TGroup!
You need to negate the height in the BITMAPINFOHEADER structure to get a top-down bitmap (i.e. one where row 0 is at the top rather than the bottom). For example:
bitmapInfo.bmiHeader.biHeight = -_imgHeight;
How can I take a screenshot of the current screen using Win32?
HDC hScreenDC = GetDC(nullptr); // CreateDC("DISPLAY",nullptr,nullptr,nullptr);
HDC hMemoryDC = CreateCompatibleDC(hScreenDC);
int width = GetDeviceCaps(hScreenDC,HORZRES);
int height = GetDeviceCaps(hScreenDC,VERTRES);
HBITMAP hBitmap = CreateCompatibleBitmap(hScreenDC,width,height);
HBITMAP hOldBitmap = static_cast<HBITMAP>(SelectObject(hMemoryDC,hBitmap));
BitBlt(hMemoryDC,0,0,width,height,hScreenDC,0,0,SRCCOPY);
hBitmap = static_cast<HBITMAP>(SelectObject(hMemoryDC,hOldBitmap));
DeleteDC(hMemoryDC);
DeleteDC(hScreenDC);
Use GetDC(NULL); to get a DC for the entire screen.
Use CreateCompatibleDC to create a DC compatible with the screen DC.
Use CreateCompatibleBitmap to create a bitmap compatible with the screen DC to hold the result.
Use SelectObject to select the compatible bitmap into the compatible DC.
Use BitBlt to copy from the screen DC to the compatible DC.
Use SelectObject to deselect the compatible bitmap from the compatible DC.
Use DeleteDC to delete the compatible DC.
When you create the compatible bitmap, you want it compatible with the screen DC, not the compatible DC.
For example:
HDC dcScreen = GetDC(0);
HDC dcTarget = CreateCompatibleDC(dcScreen);
HBITMAP bmpTarget = CreateCompatibleBitmap(dcScreen);
HGDIOBJ oldBmp = SelectObject(dcTarget, bmpTarget);
BitBlt(dcTarget, 0, 0, cx, cy, dcDesktop, x, y, SRCCOPY | CAPTUREBLT);
SelectObject(dcTarget, oldBmp);
DeleteDC(dcTarget);
ReleaseDC(dcScreen);
The other important part is to get the size, and location, of the entire virtual screen:
int x = GetSystemMetrics(SM_XVIRTUALSCREEN); //left (e.g. -1024)
int y = GetSystemMetrics(SM_YVIRTUALSCREEN); //top (e.g. -34)
int cx = GetSystemMetrics(SM_CXVIRTUALSCREEN); //entire width (e.g. 2704)
int cy = GetSystemMetrics(SM_CYVIRTUALSCREEN); //entire height (e.g. 1050)
void GetScreenShot(void)
{
int x1, y1, x2, y2, w, h;
// get screen dimensions
x1 = GetSystemMetrics(SM_XVIRTUALSCREEN);
y1 = GetSystemMetrics(SM_YVIRTUALSCREEN);
x2 = GetSystemMetrics(SM_CXVIRTUALSCREEN);
y2 = GetSystemMetrics(SM_CYVIRTUALSCREEN);
w = x2 - x1;
h = y2 - y1;
// copy screen to bitmap
HDC hScreen = GetDC(NULL);
HDC hDC = CreateCompatibleDC(hScreen);
HBITMAP hBitmap = CreateCompatibleBitmap(hScreen, w, h);
HGDIOBJ old_obj = SelectObject(hDC, hBitmap);
BOOL bRet = BitBlt(hDC, 0, 0, w, h, hScreen, x1, y1, SRCCOPY);
// save bitmap to clipboard
OpenClipboard(NULL);
EmptyClipboard();
SetClipboardData(CF_BITMAP, hBitmap);
CloseClipboard();
// clean up
SelectObject(hDC, old_obj);
DeleteDC(hDC);
ReleaseDC(NULL, hScreen);
DeleteObject(hBitmap);
}
Full code for saving a raw 24-bit lossless bitmap of all monitors at the current window station using Windows API:
BOOL WINAPI SaveBitmap(WCHAR *wPath)
{
BITMAPFILEHEADER bfHeader;
BITMAPINFOHEADER biHeader;
BITMAPINFO bInfo;
HGDIOBJ hTempBitmap;
HBITMAP hBitmap;
BITMAP bAllDesktops;
HDC hDC, hMemDC;
LONG lWidth, lHeight;
BYTE *bBits = NULL;
HANDLE hHeap = GetProcessHeap();
DWORD cbBits, dwWritten = 0;
HANDLE hFile;
INT x = GetSystemMetrics(SM_XVIRTUALSCREEN);
INT y = GetSystemMetrics(SM_YVIRTUALSCREEN);
ZeroMemory(&bfHeader, sizeof(BITMAPFILEHEADER));
ZeroMemory(&biHeader, sizeof(BITMAPINFOHEADER));
ZeroMemory(&bInfo, sizeof(BITMAPINFO));
ZeroMemory(&bAllDesktops, sizeof(BITMAP));
hDC = GetDC(NULL);
hTempBitmap = GetCurrentObject(hDC, OBJ_BITMAP);
GetObjectW(hTempBitmap, sizeof(BITMAP), &bAllDesktops);
lWidth = bAllDesktops.bmWidth;
lHeight = bAllDesktops.bmHeight;
DeleteObject(hTempBitmap);
bfHeader.bfType = (WORD)('B' | ('M' << 8));
bfHeader.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
biHeader.biSize = sizeof(BITMAPINFOHEADER);
biHeader.biBitCount = 24;
biHeader.biCompression = BI_RGB;
biHeader.biPlanes = 1;
biHeader.biWidth = lWidth;
biHeader.biHeight = lHeight;
bInfo.bmiHeader = biHeader;
cbBits = (((24 * lWidth + 31)&~31) / 8) * lHeight;
hMemDC = CreateCompatibleDC(hDC);
hBitmap = CreateDIBSection(hDC, &bInfo, DIB_RGB_COLORS, (VOID **)&bBits, NULL, 0);
SelectObject(hMemDC, hBitmap);
BitBlt(hMemDC, 0, 0, lWidth, lHeight, hDC, x, y, SRCCOPY);
hFile = CreateFileW(wPath, GENERIC_WRITE | GENERIC_READ, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
WriteFile(hFile, &bfHeader, sizeof(BITMAPFILEHEADER), &dwWritten, NULL);
WriteFile(hFile, &biHeader, sizeof(BITMAPINFOHEADER), &dwWritten, NULL);
WriteFile(hFile, bBits, cbBits, &dwWritten, NULL);
CloseHandle(hFile);
DeleteDC(hMemDC);
ReleaseDC(NULL, hDC);
DeleteObject(hBitmap);
return TRUE;
}
There is a MSDN sample, Capturing an Image, for capturing an arbitrary HWND to a DC (you could try passing the output from GetDesktopWindow to this). But how well this will work under the new desktop compositor on Vista/Windows 7, I don't know.