前面有解释了离屏渲染的相关内容,效率一般。如果电脑配置了独立显卡,真屏截图比离屏快那么一点点,如果没有GPU的服务器,结果很难堪。现在开始记录操作流程,免得忘记了细节。
第一、修改root_windows_win.c中的
void RootWindowWin::CreateRootWindow(const CefBrowserSettings& settings,bool initially_hidden)
函数;
DWORD dwStyle = WS_CHILD | WS_POPUP; //真屏截图用到
if (build_simple_browser)
{
dwStyle = WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN; //这是浏览器编译用到的
}
注:使用WS_CHILD和WS_POPUP使得整个网页填充慢窗体,方便截图。
第二、修改root_windows_win.c中
void RootWindowWin::SetBounds(int x, int y, size_t width, size_t height) {
REQUIRE_MAIN_THREAD();
if (hwnd_) {
LONG st = GetWindowLong(hwnd_, GWL_STYLE);
if (st & WS_THICKFRAME)
{
/* 突破屏幕限制 */
st = st ^ WS_THICKFRAME;
st |= WS_POPUP;
SetWindowLong(hwnd_, GWL_STYLE, st);
}
win_bounds.left = x;
win_bounds.top = y;
win_bounds.right = x + width;
win_bounds.bottom = y + height;
SetWindowPos(hwnd_, NULL, x, y, static_cast<int>(width), static_cast<int>(height), SWP_NOZORDER);
}
}
看注释就知道了,如果不修改的话,截图会受限于屏幕的尺寸。(我是翻查了别人的VB代码参考过来的)
第三、修改root_windows_win.c中
void RootWindowWin::OnPaint()
提示面页渲染更新,连续截图需要用到,具体代码给也没用什么,我这边是用共享内存设置标志的,其他的方式也行。
第四、修改root_windows_win.c中的onSize函数
void RootWindowWin::OnSize(bool minimized) {
RECT rect;
GetClientRect(hwnd_, &rect);
int width = win_bounds.right - win_bounds.left;
int height = win_bounds.bottom - win_bounds.top;
if (minimized) {
ShowWindow(hwnd_, SW_SHOWNORMAL);
MoveWindow(hwnd_, width - 30, height - 30, width, height, FALSE);
// Notify the browser window that it was hidden and do nothing further.
if (browser_window_)
{
// browser_window_->Hide();
}
return;
}
。。。。省略这段代码。。。。(client例子里面的)
}
如果没处理最小化的情况,一旦手痒点一下最小化,那恭喜你,得到一堆无用的小图。
另外还有OnCreate和onClose也要设置标志,如果不用理会也行,就读取browser->getHost()->getWindowHandle()判断一下非空就开始截图,也是可以的。
第五、修改cef_client.c的RunMain()函数:
RootWindowConfig window_config;
window_config.always_on_top = false;
window_config.with_controls = false;
window_config.with_osr = false;
window_config.initially_hidden = false;
rootWin->SetBounds(0, 0, ww, hh);
HWND hwnd_ = rootWin->GetWindowHandle();
if (hwnd_)
{
::MoveWindow(hwnd_, ww - 30 , hh - 30, ww, hh, FALSE); //放在屏幕右下角;如果不怕影响可以忽略。
}
第六、使用PrintWindow+GetDIBits截图
//为了方便理解,我抄来了保存bmp的代码,要实现内存保存也很简单的。
static void SaveHwndToBmpFile(HWND hWnd, const char *lpszPath)
{
UpdateWindow(hWnd);
HDC hDC = ::GetWindowDC(hWnd);
if (!hDC)
{
return;
}
HDC hMemDC = ::CreateCompatibleDC(hDC);
if (!hMemDC)
{
return;
}
RECT rc;
::GetWindowRect(hWnd, &rc);
HBITMAP hBitmap = ::CreateCompatibleBitmap(hDC, rc.right - rc.left, rc.bottom - rc.top);
if (!hBitmap)
{
return;
}
HBITMAP hOldBmp = (HBITMAP)::SelectObject(hMemDC, hBitmap);
//::PrintWindow(hWnd, hMemDC, 0);
::PrintWindow(hWnd, hMemDC, PW_CLIENTONLY);
//::PrintWindow(hWnd, hMemDC, 2);
BITMAP bitmap = { 0 };
::GetObject(hBitmap, sizeof(BITMAP), &bitmap);
BITMAPINFOHEADER bi = { 0 };
BITMAPFILEHEADER bf = { 0 };
CONST int nBitCount = 32;
bi.biSize = sizeof(BITMAPINFOHEADER);
bi.biWidth = bitmap.bmWidth;
bi.biHeight = bitmap.bmHeight;
bi.biPlanes = 1;
bi.biBitCount = nBitCount;
bi.biCompression = BI_RGB;
DWORD dwSize = ((bitmap.bmWidth * nBitCount + 31) / 32) * 4 * bitmap.bmHeight;
HANDLE hDib = GlobalAlloc(GHND, dwSize + sizeof(BITMAPINFOHEADER));
LPBITMAPINFOHEADER lpbi = (LPBITMAPINFOHEADER)GlobalLock(hDib);
*lpbi = bi;
::GetDIBits(hMemDC, hBitmap, 0, bitmap.bmHeight, (BYTE*)lpbi + sizeof(BITMAPINFOHEADER), (BITMAPINFO*)lpbi, DIB_RGB_COLORS);
FILE *fp = fopen(lpszPath, "wb");
bf.bfType = 0x4d42;
dwSize += sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
bf.bfSize = dwSize;
bf.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
fwrite((BYTE*)&bf, sizeof(BITMAPFILEHEADER), 1, fp);
fwrite((BYTE*)lpbi, 1, dwSize - sizeof(BITMAPFILEHEADER), fp);
fclose(fp);
GlobalUnlock(hDib);
GlobalFree(hDib);
::SelectObject(hMemDC, hOldBmp);
::DeleteObject(hBitmap);
::DeleteObject(hMemDC);
::ReleaseDC(hWnd, hDC);
}
大概就这些要点,还有一些细节可能有所忽略,要具体操作的时候才能发现吧。就这样了。。。
2023-12-15增补
目前CEF119.4.3截图超过屏幕会变成白板的,在本地电脑只需要MoveWindow一遍就可以拿到大图,但是在服务器上就必须一次拿一部分,最后合并大图,个人猜测跟服务器电脑的配置有关,建议服务器直接配无头模式(Off-screen)。