Copy DebenuQuickPDF Rendered DC to CXimage question - c++

I can't get this to save out the image, it is always 0 bytes. any ideas? thanks.
QP is from QuickPDF library and shows when rendered to screen DC but need to do it from memory.
CxImage *image = new CxImage;
int cx = QP->PageWidth();
int cy = QP->PageHeight();
CDC *pDC = GetDC();
CDC m_hMemDC;
m_hMemDC.CreateCompatibleDC(pDC);
QP->RenderPageToDC(76/2, 1, m_hMemDC); // from quick pdf. dpi, page 1. when I do this to screen it shows ok.
HDC hSaveDC = CreateCompatibleDC(m_hMemDC);
HBITMAP hBitmap = CreateCompatibleBitmap(m_hMemDC, cx, cy);
HBITMAP hSaveBitmap = (HBITMAP)SelectObject(hSaveDC, (HGDIOBJ)hBitmap);
BitBlt(hSaveDC, 0, 0, cx, cy, m_hMemDC, 0, 0, SRCCOPY);
hBitmap = (HBITMAP)SelectObject(hSaveDC, (HBITMAP)hSaveBitmap);
image->CreateFromHBITMAP(hBitmap);
image->Save("c:\\test.jpg", CXIMAGE_FORMAT_JPG); // 0 bytes.

Related

c++ Save application window to file yield 'incorrect' area?

I'm slowly making progress in my c++ application.
I would like to 'automate' the repetitive manual screen capture I go through every time in support of my debugging process. I'm relatively new to c++ so I found this snippet that seems to work fine for me, except it's not quite capturing my application area :
RECT rectWindow;
GetWindowRect(hWnd, &rectWindow); // hWnd defined in global scope
int x1, y1, x2, y2, w, h;
x1 = rectWindow.left; // x1 = 780
y1 = rectWindow.top; // y1 = 385
x2 = rectWindow.right; // x2 = 1780
y2 = rectWindow.bottom; // y2 = 1055
// width and height
w = x2 - x1; // w = 1000
h = y2 - y1; // h = 670
// copy window to bitmap
HDC hWindow = GetDC(hWnd);
HDC hDC = CreateCompatibleDC(hWindow);
HBITMAP hBitmap = CreateCompatibleBitmap(hWindow, w, h);
HGDIOBJ old_obj = SelectObject(hDC, hBitmap);
BOOL bRet = BitBlt(hDC, 0, 0, w, h, hWindow, x1, y1, SRCCOPY);
CImage image;
image.Attach(hBitmap);
image.Save(L"C:\\TEMP\\window.bmp", Gdiplus::ImageFormatBMP);
// clean-up
SelectObject(hDC, old_obj);
DeleteDC(hDC);
::ReleaseDC(NULL, hWindow);
DeleteObject(hBitmap);
Below is a manual screen capture of my application window :
Now, this (saved in JPEG to meet SO min size requirements) is what I'm getting :
Not quite what I expect.
So I edited the Bit Block call this way :
BOOL bRet = BitBlt(hDC, 0, 0, w, h, hWindow, 0, 0, SRCCOPY);
And this gives me "almost" the correct capture :
What am doing wrong?
GetWindowRect() returns screen coordinates, which you are then passing as-is to BitBlt(), but it is expecting coordinates relative to the window instead since you are capturing from a window DC and not a screen DC. That is why the top-left corner of your capture is so far into the window.
Change this:
BitBlt(..., hWindow, x1, y1, ...)
To this instead:
BitBlt(..., hWindow, 0, 0, ...)
Also, GetDC() retrieves an HDC for just the client area of the window:
The GetDC function retrieves a handle to a device context (DC) for the client area of a specified window or for the entire screen.
Your window's title bar and menu do not appear in your capture because they are outside of the client area. Since you want a capture of the entire window, use GetWindowDC() instead:
The GetWindowDC function retrieves the device context (DC) for the entire window, including title bar, menus, and scroll bars. A window device context permits painting anywhere in a window, because the origin of the device context is the upper-left corner of the window instead of the client area.
Found some code that worked flawlessly.
It's using a lot of the same code?
RECT rcSrc;
HWND hSrcWnd;
HDC hDC2, hSrcDC;
HBITMAP hBmp;
//hSrcWnd = FindWindow(NULL, szWindowClass);
GetWindowRect(hWnd, &rcSrc);
hDC2 = GetDC(hWnd);
hSrcDC = CreateCompatibleDC(NULL);
hBmp = CreateCompatibleBitmap(hDC2, rcSrc.right - rcSrc.left, rcSrc.bottom - rcSrc.top);
SelectObject(hSrcDC, hBmp);
PrintWindow(hWnd, hSrcDC, 0);
BitBlt(hDC2, 0, 0, rcSrc.right - rcSrc.left, rcSrc.bottom - rcSrc.top, hSrcDC, 0, 0, SRCCOPY);
CImage image2;
image2.Attach(hBmp);
image2.Save(L"C:\\TMP\\Test.jpg", Gdiplus::ImageFormatJPEG);
DeleteObject(hBmp);
DeleteDC(hSrcDC);
ReleaseDC(hWnd, hDC2);
The result is this :

Taking a screenshot in Windows will output a black screen image

I'm trying to take a full page screenshot in windows. function works in first call but after second call won't work at all and it's just getting a black screen image with a stable size.
when i use debugger the function works well without giving the black screen.
Here is the code:
void screenshot(std::string imageaPath)
{
ULONG_PTR gdiplustoken;
Gdiplus::GdiplusStartupInput gdistartupinput;
Gdiplus::GdiplusStartupOutput gdistartupoutput;
gdistartupinput.SuppressBackgroundThread = true;
GdiplusStartup(&gdiplustoken, &gdistartupinput, &gdistartupoutput); //start GDI+
HDC hScreenDC = GetDC(GetDesktopWindow());
HDC hMemoryDC = CreateCompatibleDC(hScreenDC);
int cx = GetSystemMetrics(SM_CXVIRTUALSCREEN);
int cy = GetSystemMetrics(SM_CYVIRTUALSCREEN);
int x = GetSystemMetrics(SM_XVIRTUALSCREEN);
int y = GetSystemMetrics(SM_YVIRTUALSCREEN);
HBITMAP hbitmap = CreateCompatibleBitmap(hScreenDC, cx, cy);
HBITMAP holdbitmap = static_cast<HBITMAP>(SelectObject(hMemoryDC, hbitmap));
BitBlt(hMemoryDC, 0, 0, cx, cy, hScreenDC, x, y, SRCCOPY | CAPTUREBLT);
hbitmap = static_cast<HBITMAP>(SelectObject(hMemoryDC, holdbitmap));
UINT num, size;
Gdiplus::ImageCodecInfo* imagecodecinfo;
Gdiplus::GetImageEncodersSize(&num, &size); // get count of codec
imagecodecinfo = (Gdiplus::ImageCodecInfo*)(malloc(size));
GetImageEncoders(num, size, imagecodecinfo);//get codec
CLSID clsidEncoder;
for (int i = 0; i < num; i++)
{
if (wcscmp(imagecodecinfo[i].MimeType, L"image/jpeg") == 0)
clsidEncoder = imagecodecinfo[i].Clsid; // get jpeg codec id
}
free(imagecodecinfo);
Gdiplus::Bitmap* bm = new Gdiplus::Bitmap(hbitmap, NULL);
std::wstring ws;
ws.assign(imageaPath.begin(), imageaPath.end());//sring to wstring
bm->Save(ws.c_str(), &clsidEncoder); //save in jpeg format
SelectObject(hMemoryDC, holdbitmap);//Release Objects
DeleteObject(hMemoryDC);
DeleteObject(hbitmap);
ReleaseDC(GetDesktopWindow(), hScreenDC);
Gdiplus::GdiplusShutdown(gdiplustoken);
}
update:
Okay i find a way to take a screenshot without black screen image
when i use system("pause"); to make program stop and when press enter to make program continue, it's working, I used c++ sleep methods but not works, any idea?
...
HBITMAP holdbitmap = static_cast<HBITMAP>(SelectObject(hMemoryDC, hbitmap));
system("pause");
BitBlt(hMemoryDC, 0, 0, cx, cy, hScreenDC, x, y, SRCCOPY | CAPTUREBLT);
...
sleep methods:
Sleep(1000);
std::this_thread::sleep_for(std::chrono::seconds(1));
update 2:
I was sending screenshot request using curl and I was testing in a server(rdp) and I was logged out when i was sending request, I think sleep mode in server is enabled and when I logged out the server will be sleep and it's like computer screen to go dark and that's why BitBlt() fails and GetLastError() will return 5 which means access denied
The documentation for GdiplusShutdown says that
You must call GdiplusStartup before you create any GDI+ objects, and you must delete all of your GDI+ objects (or have them go out of scope) before you call GdiplusShutdown.
You are leaking bm = new Gdiplus::Bitmap(...) which is violating this rule.

How to turn a screenshot bitmap to a cv::Mat

I currently am trying to take a screenshot of the screen, and then get it into a format editable by OpenCV. The code I'm using is from the microsoft website, https://learn.microsoft.com/en-gb/windows/win32/gdi/capturing-an-image. The code uses the "Windows.h" library. The easiest way of doing it is obviously to just save the bitmap as a .bmp, then open it using opencv. However, I would like it to be more efficient than that, and I don't know how to. When I used the code, it outputted a char pointer, which I don't know how to convert to a cv::Mat. The code is below:
cv::Mat * Capture::GetMat()
{
cv::Mat * mat1;
MemoryHandle = NULL;
BitmapHandle = NULL;
// Find the handle for the device context of the entire screen, and the specific window specified.
ScreenHandle = GetDC(NULL);
WindowHandle = GetDC(hwnd);
//Make the compatible DC (Device Context) for storing the data in memory.
MemoryHandle = CreateCompatibleDC(WindowHandle);
//Make a compatible DC for the bitmap to be stored in.
BitmapHandle = CreateCompatibleBitmap(WindowHandle, width, height);
//Select the correct bitmap, and put it into memory using the memory handle.
SelectObject(MemoryHandle, BitmapHandle);
//Transfer the actual bitmap into the compatible memory DC.
BitBlt(MemoryHandle, 0, 0, 1920, 1080, WindowHandle, 0, 0, SRCCOPY);
//Get the bitmap from the handle, and ready it to be filed.
GetObject(BitmapHandle, sizeof(BITMAP), &Bitmap);
//Cofinguring INFO details.
bmpInfoHeader.biSize = sizeof(BITMAPINFOHEADER);
bmpInfoHeader.biWidth = Bitmap.bmWidth;
bmpInfoHeader.biHeight = Bitmap.bmHeight;
bmpInfoHeader.biPlanes = 1;
bmpInfoHeader.biBitCount = 32;
bmpInfoHeader.biCompression = BI_RGB;
bmpInfoHeader.biSizeImage = 0;
bmpInfoHeader.biXPelsPerMeter = 0;
bmpInfoHeader.biYPelsPerMeter = 0;
bmpInfoHeader.biClrUsed = 0;
bmpInfoHeader.biClrImportant = 0;
bmpSize = ((Bitmap.bmWidth * bmpInfoHeader.biBitCount + 31) / 32) * 4 * Bitmap.bmHeight;
memhnd = GlobalAlloc(GHND, bmpSize);
mat1 = (cv::Mat *)GlobalLock(memhnd);
std::cout << GetLastError() << std::endl;
return mat1;
}
int Capture::save_mat(cv::Mat * mat)
{
std::string FileName("P:/Photos/capture");
FileName += std::to_string(image_count_mat);
FileName += (const char*)(".jpg");
cv::Mat mat2 = *mat;
cv::imwrite(FileName.c_str(), mat2);
image_count_mat++;
return 0;
}
The class has these attributes:
private:
HWND hwnd;
HDC hdc;
int image_count_bitmap = 0;
int image_count_mat = 0;
int height;
int width;
HDC ScreenHandle;
HDC WindowHandle;
HDC MemoryHandle = NULL;
HBITMAP BitmapHandle = NULL;
BITMAP Bitmap;
BITMAPFILEHEADER bmpFileHeader;
BITMAPINFOHEADER bmpInfoHeader;
DWORD bmpSize;
HANDLE memhnd;
The GetMat() function works fine and doesn't output an error, although I have no idea how to check if the outputted cv::Mat is correct. When I run the save_mat() function however, the program crashes.
It is not recommended to store device context handles. For example a call to GetDC should be followed by ReleaseDC as soon as you are finished with the handle. You can store bitmap handles and memory dc, but in most cases it is not necessary.
Once you have copied the image in to bitmap, use GetDIBits to copy the bits in to cv::Mat as shown in the example below.
Note that your application needs DPI compatibility, for example with SetProcessDPIAware to find the correct desktop size.
This example uses 32-bit bitmap with CV_8UC4, but these GDI functions are 24-bit. You can also use 24-bit bitmap with CV_8UC3.
void screenshot()
{
auto w = GetSystemMetrics(SM_CXFULLSCREEN);
auto h = GetSystemMetrics(SM_CYFULLSCREEN);
auto hdc = GetDC(HWND_DESKTOP);
auto hbitmap = CreateCompatibleBitmap(hdc, w, h);
auto memdc = CreateCompatibleDC(hdc);
auto oldbmp = SelectObject(memdc, hbitmap);
BitBlt(memdc, 0, 0, w, h, hdc, 0, 0, SRCCOPY);
cv::Mat mat(h, w, CV_8UC4);
BITMAPINFOHEADER bi = { sizeof(bi), w, -h, 1, 32, BI_RGB };
GetDIBits(hdc, hbitmap, 0, h, mat.data, (BITMAPINFO*)&bi, DIB_RGB_COLORS);
cv::imwrite("screenshot.png", mat);
SelectObject(memdc, oldbmp);
DeleteDC(memdc);
DeleteObject(hbitmap);
ReleaseDC(HWND_DESKTOP, hdc);
}

Anti aliasing in MFC

I'm trying to implement anti-aliasing in my MFC app, I'm using the technique described in this tutorial.
Create a bitmap (2x, 4x, 8x) the size of the original bitmap.
Draw on the resized bitmap (I'm only using simple figures (lines, circles and etc)).
Set StretchBlt Mode to HalfTone.
And Resize with StretchBlt to the original size.
Using this way, drawing in the resized bitmap it works, but I want to create a more generic function that receives a bitmap with the drawing already made and return with the anti-aliasing, I tried this:
static HBITMAP AntiAliasing(HBITMAP hBitmap)
{
int escala = 4;
HBITMAP bmp = __copia(hBitmap); // Copy the bitmap.
HDC hMemDC = CreateCompatibleDC(NULL);
HBITMAP bmpAntigo1 = (HBITMAP)::SelectObject(hMemDC, bmp);
BITMAP bitmap;
::GetObject(hBitmap, sizeof(BITMAP), &bitmap);
// Create a bitmap (2x, 4x, 8x) the size of the original bitmap.
HDC hDCDimensionado = ::CreateCompatibleDC(hMemDC);
HBITMAP bmpDimensionado = ::CreateCompatibleBitmap(hDCDimensionado,
bitmap.bmWidth * escala,
bitmap.bmHeight * escala);
HBITMAP hBmpVelho = (HBITMAP)::SelectObject(hDCDimensionado, bmpDimensionado);
// I also tried with {BLACKONWHITE, HALFTONE, WHITEONBLACK}
int oldStretchBltMode2 = ::SetStretchBltMode(hDCDimensionado, COLORONCOLOR);
// Resize the bitmap to the new size.
::StretchBlt(hDCDimensionado,
0, 0, bitmap.bmWidth * escala, bitmap.bmHeight * escala,
hMemDC,
0, 0, bitmap.bmWidth, bitmap.bmHeight,
SRCCOPY);
/*
* Here the bitmap has lost his colors and became black and white.
*/
::SetStretchBltMode(hDCDimensionado, oldStretchBltMode2);
// Set StretchBltMode to halfTone so can mimic the anti aliasing effect.
int oldStretchBltMode = ::SetStretchBltMode(hMemDC, HALFTONE);
// resize to the original size.
::StretchBlt(hMemDC,
0, 0, bitmap.bmWidth, bitmap.bmHeight,
hDCDimensionado,
0, 0, escala * bitmap.bmWidth, escala * bitmap.bmHeight,
SRCCOPY);
::SetStretchBltMode(hMemDC, oldStretchBltMode);
::SelectObject(hMemDC, bmpAntigo1);
::DeleteDC(hMemDC);
::SelectObject(hDCDimensionado, hBmpVelho);
DeleteDC(hDCDimensionado);
return bmp;
}
But this function doesn't work, the result loses its colors (all drawings became black) and there isn't anti aliasing.
Any help will be appreciated!
From documentation for CreateCompatibleBitmap:
Note: When a memory device context is created, it initially has a
1-by-1 monochrome bitmap selected into it. If this memory device
context is used in CreateCompatibleBitmap, the bitmap that is created
is a monochrome bitmap. To create a color bitmap, use the HDC that was
used to create the memory device context, as shown in the following
code:
Change the code and supply hdc for the desktop as show below:
HDC hdc = ::GetDC(0);
HBITMAP bmpDimensionado = ::CreateCompatibleBitmap(hdc, ...)
::ReleaseDC(0, hdc);
This will show the image, however this method will not produce the desired effect because it simply magnifies each pixel to larger size and reduces it back to the original pixel. There is no blending with neighboring pixels.
Use other methods such Direct2D with Gaussian blur effect, or use GDI+ instead with interpolation mode:
Gdiplus::GdiplusStartup...
void foo(HDC hdc)
{
Gdiplus::Bitmap bitmap(L"file.bmp");
if(bitmap.GetLastStatus() != 0)
return 0;
auto w = bitmap.GetWidth();
auto h = bitmap.GetHeight();
auto maxw = w * 2;
auto maxh = h * 2;
Gdiplus::Bitmap membmp(maxw, maxh);
Gdiplus::Graphics memgr(&membmp);
memgr.SetInterpolationMode(Gdiplus::InterpolationModeHighQualityBilinear);
memgr.DrawImage(&bitmap, 0, 0, maxw, maxh);
Gdiplus::Graphics gr(hdc);
gr.SetInterpolationMode(Gdiplus::InterpolationModeHighQualityBilinear);
gr.DrawImage(&membmp, 0, 0, w, h);
}
If target window is at least Vista, use GDI+ version 1.1 with blur effect. See also How to turn on GDI+ 1.1 in MFC project
#define GDIPVER 0x0110 //add this to precompiled header file
void blur(HDC hdc)
{
Gdiplus::Graphics graphics(hdc);
Gdiplus::Bitmap bitmap(L"file.bmp");
if(bitmap.GetLastStatus() != 0)
return;
Gdiplus::Blur blur;
Gdiplus::BlurParams blur_param;
blur_param.radius = 3; //change the radius for different result
blur_param.expandEdge = TRUE;
blur.SetParameters(&blur_param);
bitmap.ApplyEffect(&blur, NULL);
graphics.DrawImage(&bitmap, 0, 0);
}

Crop function BitBlt(...)

I want to create a crop function in an existing engine. This is what I already have:
bool Bitmap::Crop(RECT cropArea)
{
BITMAP bm;
GetObject(m_Handle, sizeof(bm), &bm);
HDC hSrc = CreateCompatibleDC(NULL);
SelectObject(hSrc, m_Handle);
HDC hNew = CreateCompatibleDC(NULL);
HBITMAP hBmp = CreateCompatibleBitmap(hNew, bm.bmWidth, bm.bmHeight);
HBITMAP hOld = (HBITMAP)SelectObject(hNew, hBmp);
BitBlt(hNew, 0, 0, bm.bmWidth, bm.bmHeight, hSrc, 0, 0, SRCCOPY);
SelectObject(hNew, hOld);
DeleteDC(hSrc);
DeleteDC(hNew);
DeleteObject(m_Handle);
m_Handle = hBmp;
}
I want it to just copy the whole image to a new HBITMAP and replace the old with it. So I know that it works. After that it is just playing with the BitBlt parameters.
m_Handle is a HBITMAP of the class Bitmap.
The result of this code is just a black screen.
Thanks for helping me.
The function works perfectly now.
bool Bitmap::Crop(RECT cropArea)
{
HDC hSrc = CreateCompatibleDC(NULL);
SelectObject(hSrc, m_Handle);
HDC hNew = CreateCompatibleDC(hSrc);
HBITMAP hBmp = CreateCompatibleBitmap(hSrc, cropArea.right - cropArea.left, cropArea.bottom - cropArea.top);
HBITMAP hOld = (HBITMAP)SelectObject(hNew, hBmp);
bool retVal = (BitBlt(hNew, 0, 0, cropArea.right - cropArea.left, cropArea.bottom - cropArea.top, hSrc, cropArea.left, cropArea.top, SRCCOPY))?true:false;
SelectObject(hNew, hOld);
DeleteDC(hSrc);
DeleteDC(hNew);
DeleteObject(m_Handle);
m_Handle = hBmp;
return retVal;
}
Never create a compatible bitmap from a 'fresh' memory DC.
Unless that is you WANT to create a 1bpp bitmap - the default bitmap selected in a new memory DC is a 1x1 1bpp bitmap - so any compatible bitmap you create will match that.
Which does tend to result in all black output.
Your color bitmap in in hSrc, so use that dc to make the new bitmap.
Two small changes:
HBITMAP hBmp = CreateCompatibleBitmap(hNew, cropArea.right - cropArea.left, cropArea.bottom - cropArea.top);
BitBlt(hNew, 0, 0, cropArea.right - cropArea.left, cropArea.bottom - cropArea.top, hSrc, cropArea.left, cropArea.top, SRCCOPY);
You might want a little more checking to make sure the requested area falls within the size of the original bitmap.