先说一点题外话,将WEB页面渲染成图片有比较好的开源工具,如CutyCapt ,它使用WebKit渲染,兼容多种操作系统,适合于在服务器上作为后台服务运行。
不过,这里说到的是对WebBrowser内的页面进行截图并保存. WebBrowser本质上就是IE内核的浏览器。使用mshtml来渲染页面的话,依赖GDI,所以不可能作为后台服务运行。
获取WebBrowser截屏的方法很多, PrintWindow / IHTMLElementRender / IViewObject。不管使用哪种方法,都需考虑长页面的问题。因为这些方法都只能截屏clientArea区域,也就是说没显示的部分无法截图,必须通过多次截图完成整个页面截图的拼合。
本文使用的是PrintWindow方式,这种方式原理上能够兼容其它所有的浏览器。
首先通过设置ControlSite将浏览器的边框和滚动条隐藏,这样客户区只有WEB页面。
HRESULT FAR EXPORT CCustomControlSite::XDocHostUIHandler::GetHostInfo( DOCHOSTUIINFO* pInfo ) { METHOD_PROLOGUE(CCustomControlSite, DocHostUIHandler) pInfo->cbSize = sizeof(DOCHOSTUIINFO); pInfo->dwFlags = DOCHOSTUIFLAG_DIALOG | DOCHOSTUIFLAG_DISABLE_HELP_MENU | DOCHOSTUIFLAG_ACTIVATE_CLIENTHIT_ONLY | DOCHOSTUIFLAG_URL_ENCODING_ENABLE_UTF8 | DOCHOSTUIFLAG_NO3DOUTERBORDER | DOCHOSTUIFLAG_NO3DBORDER | DOCHOSTUIFLAG_SCROLL_NO | DOCHOSTUIFLAG_USE_WINDOWLESS_SELECTCONTROL; pInfo->dwDoubleClick = DOCHOSTUIDBLCLK_DEFAULT; return S_OK; }
void CWebBrowser::OnDocumentComplete( IDispatch *pDisp, VARIANT *URL) { CComQIPtr<IWebBrowser2> pWebBrowser2(pDisp); if( pWebBrowser2 ) { CComPtr<IDispatch> pDispatch; if( S_OK == pWebBrowser2->get_Document(&pDispatch) ) { CComQIPtr<IHTMLDocument2> pDoc2(pDispatch); if( pDoc2 ) { BSTR bstrReadyState; if( S_OK == pDoc2->get_readyState(&bstrReadyState) ) { if( 0 == _wcsicmp( bstrReadyState, L"complete") ) { CComPtr<IHTMLWindow2> pWnd2; if( S_OK == pDoc2->get_parentWindow(&pWnd2) ) { CComPtr<IHTMLWindow2> pTopWnd2; if( S_OK == pWnd2->get_top(&pTopWnd2) ) { CComQIPtr<IHTMLWindow3> pTopWnd3(pTopWnd2); if( pTopWnd3 ) { VARIANT_BOOL vbSuccess = VARIANT_FALSE; if( m_pOnPageLoadEvent ) { pTopWnd3->detachEvent( _bstr_t(L"onload"), m_pOnPageLoadEvent); } m_pOnPageLoadEvent = (CDOMEventHandler*)CDOMEventHandler::CreateEventHandler( &CWebBrowser::OnPageLoad, (LONG_PTR)this); pTopWnd3->attachEvent( _bstr_t(L"onload") , m_pOnPageLoadEvent , &vbSuccess ); } } } } SysFreeString(bstrReadyState); } }// pDoc2 }// get_Document }// pWebBrowser2 }
首先创建一个大小等于 document.body.clientWidth 宽, document.documentElement.scrollHeight 高的 画布。 然后依次滚动页面,每次滚动的距离等于客户区的高度,滚动后截图,依此结束。
void CWebBrowser::CaptureToImage() { CComQIPtr<IHTMLDocument2> pDoc2 = this->get_Document(); CComQIPtr<IHTMLDocument3> pDoc3(pDoc2); if( pDoc2 ) { CComPtr<IHTMLElement> pBodyElem; CComPtr<IHTMLWindow2> pWnd2, pTopWnd2; if( S_OK == pDoc2->get_body(&pBodyElem) && S_OK == pDoc2->get_parentWindow(&pWnd2) && S_OK == pWnd2->get_top(&pTopWnd2)) { long nScrollHeight = 0L, nClientWidth = 0L, nClientHeight = 0L; CComPtr<IHTMLElement> pDocElem; pDoc3->get_documentElement(&pDocElem); CComQIPtr<IHTMLElement2> pDocElem2(pDocElem); CComQIPtr<IHTMLElement2> pBodyElem2(pBodyElem); pBodyElem2->get_scrollHeight(&nScrollHeight); RECT rect; GetClientRect(&rect); nClientWidth = rect.right - rect.left; nClientHeight = rect.bottom - rect.top; if( nScrollHeight > 0 && nClientWidth > 0 && nClientHeight > 0 ) { Bitmap bitmap(nClientWidth, nScrollHeight); Graphics g(&bitmap); HDC hDC = g.GetHDC(); if (hDC != NULL) { long nYPos = nScrollHeight - nClientHeight; do { pTopWnd2->scrollTo( 0, nYPos); { long y1 = 0, y2 = 0; pDocElem2->get_scrollTop(&y1); pBodyElem2->get_scrollTop(&y2); nYPos = max(y1, y2); } HDC hMemDC = ::CreateCompatibleDC(hDC); HBITMAP hBitmap = ::CreateCompatibleBitmap( hDC, nClientWidth, nClientHeight); ::SelectObject( hMemDC, hBitmap); VERIFY(::PrintWindow( GetSafeHwnd(), hMemDC, PW_CLIENTONLY)); ::SelectObject( hMemDC, NULL); ::BitBlt( hDC, 0, nYPos, nClientWidth, nClientHeight, hMemDC, 0, 0, SRCCOPY); ::DeleteDC(hMemDC); ::DeleteObject(hBitmap); if( nYPos <= 0) break; nYPos -= nClientHeight; if( nYPos < 0 ) nYPos = 0; } while (true); g.ReleaseHDC(hDC); CLSID pngClsid; GetEncoderClsid(L"image/png", &pngClsid); bitmap.Save(L"L:\\WebSitesMonitoring\\1.png", &pngClsid); } } } } }
int CWebBrowser::GetEncoderClsid(const WCHAR* format, CLSID* pClsid) { UINT num = 0; // number of image encoders UINT size = 0; // size of the image encoder array in bytes ImageCodecInfo* pImageCodecInfo = NULL; GetImageEncodersSize(&num, &size); if(size == 0) return -1; // Failure pImageCodecInfo = (ImageCodecInfo*)(malloc(size)); if(pImageCodecInfo == NULL) return -1; // Failure GetImageEncoders(num, size, pImageCodecInfo); for(UINT j = 0; j < num; ++j) { if( wcscmp(pImageCodecInfo[j].MimeType, format) == 0 ) { *pClsid = pImageCodecInfo[j].Clsid; free(pImageCodecInfo); return j; // Success } } free(pImageCodecInfo); return -1; // Failure }