最近,有朋友总要跟我PK QQ美女找茬,无奈在下眼力实在是不如人。不过,咱可是计算机专业的啊,自己找不过他,还不能利用计算机来找吗?嘿嘿,于是开始研究这个辅助工具。
(资料图片)
首先,先看看截图:
下面说说制作的方法。我想,大家应该也能想通制作的方法:获取窗口句柄->找到图片(两张)->对比->设置成不同的颜色->显示出来。
过程是很简单的,先看看第一步,获取窗口句柄
Code: // 获取游戏句柄 bool CMain::GetGameHandle(void) { m_pGame = FindWindow(NULL, _T("大家来找茬")); if(m_pGame == NULL) return false; else return true; }
这里用到了一个FindWindow()函数,函数原型如下
HWND FindWindow(LPCSTR lpClassName,LPCSTR lpWindowName);
利用窗口名称获取到一个窗口HWND.
然后,很重要的就是获取图片了,把图片要先存入一个缓冲区,以下是代码:
Code: // 将游戏中位图数据复制到内存中 bool CMain::CopyPicToBlt(DWORD*& lpvBits, int xSrc, int ySrc, int nWidth, int nHeight) { HWND hWnd = m_pGame;//主游戏句柄 HDC hSrcDC=NULL; HDC hNewDC; //图片格式信息头 BITMAPINFOHEADER bi; bi.biSize = sizeof(BITMAPINFOHEADER); bi.biWidth = nWidth; bi.biHeight = nHeight; bi.biPlanes = 1; bi.biBitCount = 32; bi.biCompression = BI_RGB; bi.biSizeImage = nWidth*nHeight; bi.biXPelsPerMeter = 0; bi.biYPelsPerMeter = 0; bi.biClrUsed = 0; bi.biClrImportant = 0; HBITMAP pBitmap; hSrcDC=GetDC(hWnd);//获取程序DC hNewDC = CreateCompatibleDC(hSrcDC);//创建兼容DC pBitmap = CreateCompatibleBitmap(hSrcDC, nWidth, nHeight);//设置图大小 SelectObject(hNewDC, pBitmap); //绑定图片 //将位图复制到DC中 BitBlt(hNewDC, 0, 0, nWidth, nHeight, hSrcDC, xSrc, ySrc, SRCCOPY); //为图片申请一块内存空间 if(lpvBits) { delete[] lpvBits; lpvBits=NULL; } lpvBits = new DWORD[nWidth*nHeight]; if(!lpvBits) { return false; } //将图片数据存储到对应的变量中 GetDIBits(hNewDC, pBitmap, 0, (UINT)m_nPicHeight, lpvBits, (BITMAPINFO *)&bi, DIB_RGB_COLORS); DeleteObject(pBitmap); DeleteDC(hNewDC); return true; }
这里,要先简单说下BMP图片的知识。BMP图片其实在计算机里也是以二进制的方式存储的(任何文件其实都是),这样,我们用一个DWORD指针来作为BUFFER,方便比较。通过这个函数,就把窗口中(xSrc, ySrc)位置的(nWidth, nHeight)大小的位图存储到了lpvBits指向的缓冲区了,以后直接比较两个缓冲区的内容即可。
Code: // 比较两个图片 void CMain::CompareBMP(DWORD*& pBuffer, DWORD* pLeft, DWORD* pRight) { if(!pBuffer) { delete[] pBuffer; pBuffer=NULL; } pBuffer = new DWORD[m_nPicWidth*m_nPicHeight]; //比较两幅图,数据相同的设置为白色,不同的为红色。 for (DWORD i = 0; i < (DWORD)m_nPicWidth * m_nPicHeight; i++) { if(pLeft[i] != pRight[i]) pBuffer[i] = RGB(0,0,255); else pBuffer[i] = RGB(255,255,255); } //SaveBMP(pBuffer, _T("d://result1.bmp") ); //转换图片数据,使图片按正常顺序显示。(BMP格式与窗口图像存放竖坐标相反) int width = m_nPicWidth; for (DWORD i = 0; i < (DWORD)(m_nPicHeight / 2); i++) { for (DWORD j = 0; j < (DWORD)width; j++) { DWORD temp; temp = *(pBuffer + i * width + j); *(pBuffer + i * width + j) = *(pBuffer + width * (m_nPicHeight - 1 - i) + j); *(pBuffer + width * (m_nPicHeight - 1 - i) + j) = temp; } } //SaveBMP(pBuffer, _T("d://result2.bmp") ); if(pLeft) { delete[] pLeft; pLeft = NULL; } if(pRight) { delete[] pRight; pRight = NULL; } }
最后通过上面的来比较图片。当然,这个算法是最简单的算法,就是利用循环,按顺序比较,再将结果填充到一个新的缓冲区里,把原先的两张图片缓冲区内内容清除掉
然后,就是显示出来了,首先要先创建一个半透明的对话框
Code: // 点击查找后 void CFindFaultDlg::FindFault(void) { if(!m_GameMain.GetGameHandle()) { MessageBox( _T("请先打开"QQ美女找茬"游戏"), _T("温情提示")); return; } DWORD* pBuffer=NULL; //创建一个对话框,用于显示找茬游戏中两幅图的不同点 if(m_pDlg) m_pDlg->DestroyWindow(); m_GameMain.FindingFault(pBuffer); m_pDlg = new CDialog(); if(m_pDlg) { if(!m_pDlg->Create(IDD_DLGSHOW, this)) { MessageBox( _T("对话框初始化失败"), _T("温情提示") ); return ; } //利用SetLayeredWindowAttributes设置窗口透明。 //自带的SDK不支持,需要更新才能直接使用这个函数。 //SetWindowLong(this->GetSafeHwnd(),GWL_EXSTYLE,GetWindowLong(this->GetSafeHwnd(),GWL_EXSTYLE)^WS_EX_LAYERED); //SetLayeredWindowAttributes(this->m_hWnd,0,128,2); // //动态的从User32.dll中取得SetLayeredWindowAttributes函数地址。WS_EX_LAYERED = 0x80000 SetWindowLong(m_pDlg->GetSafeHwnd(),GWL_EXSTYLE, GetWindowLong(m_pDlg->GetSafeHwnd(),GWL_EXSTYLE)^0x80000| WS_EX_TRANSPARENT); HINSTANCE hInst=LoadLibrary(_T("User32.DLL")); if(hInst) { typedef BOOL (WINAPI * MYFUNC)(HWND,COLORREF,BYTE,DWORD); MYFUNC fun=NULL; //取得SetLayeredWindowAttributes函数指针 fun=(MYFUNC)GetProcAddress(hInst,"SetLayeredWindowAttributes"); if(fun) fun(m_pDlg->GetSafeHwnd(),0,128,2); FreeLibrary(hInst); } //移动该对话框窗口,置顶,覆盖找茬游戏界面中右边的那幅图。 CRect rect; CWnd* pGameWnd = FindWindow(NULL, _T("大家来找茬")); pGameWnd->GetClientRect(&rect); pGameWnd->ClientToScreen(&rect); if(!m_pDlg->SetWindowPos(&wndTopMost, rect.left + m_ShowOffsetX, rect.top + m_ShowOffsetY, 497, 448, SWP_SHOWWINDOW)) { MessageBox(_T("设置对话框失败!"), _T("温情提示")); return; } } else { MessageBox(_T("创建对话框对象失败!"), _T("温情提示")); return; } //显示不同 CDC* pShow = m_pDlg->GetDC(); ShowFault(pShow,pBuffer); //m_GameMain.SaveBMP(pBuffer, _T("D://result3.bmp") ); if(pBuffer) { delete[] pBuffer; pBuffer = NULL; } }
上面的函数不仅显示了对话框,而且将位置通过SetWindowPos()调整到当前游戏窗口中图片的上方
然后就是把不同的地方显示出来
Code: // 显示不同的地方 void CFindFaultDlg::ShowFault(CDC* pDC, DWORD* pBuffer) { CBitmap bm; bm.CreateBitmap(m_GameMain.GetPicFrame().x,m_GameMain.GetPicFrame().y,1,32, pBuffer); //bm.LoadBitmap(_T("I://result.bmp")); CBrush brush; brush.CreatePatternBrush(&bm); CBrush* pOldBrush = (CBrush*)pDC->SelectObject(&brush); pDC->FillRect(&CRect(0, 0, m_GameMain.GetPicFrame().x,m_GameMain.GetPicFrame().y),&brush); pDC->SelectObject(pOldBrush); brush.DeleteObject(); bm.DeleteObject(); ReleaseDC(pDC); }
这里是用刚才比较结束后的缓冲区内容的位图信息创建了一个画刷brush,然后用该画刷填充整个窗口,这样就结束了。
后记:通过制作这个小小的辅助工具,了解了获取句柄的方法,遇到了许多问题,比如32位位图和24位位图就有很大区别。
还有半透明窗口的显示。而且,最后遇到了一个很诡异的问题,这个程序到这里在我自己的电脑上都很正确,可是当我在我家的电脑上使用的时候,就总是在中间多出来一个方块的东西,而且只能显示一次,再点显示也是无效,无奈在家电脑也装上VS来调试,调试的时候也没发现什么问题,该执行的语句都执行了。如果有哪位高手知道这个问题的答案,还望多多指教。顺便说一句,我的系统是Win7,家中的是Vista。
为了解决这个问题,我最后采用了另外一种方法:不是通过截取窗口,而是截取屏幕,结果就正常了。。。
Code: // 通过截屏来获取图像 bool CMain::GetPicByCap(DWORD*& lpvbits, int xSrc, int ySrc, int nWidth, int nHeight) { HWND hWnd = m_pGame; //得到句柄 HDC hdcScreen; HDC hdcWindow; HDC hdcMemDC = NULL; HBITMAP hbmScreen = NULL; BITMAP bmpScreen; // Retrieve the handle to a display device context for the client // area of the window. hdcScreen = GetDC(NULL); hdcWindow = GetDC(hWnd); hdcMemDC = CreateCompatibleDC(hdcWindow); if(!hdcMemDC) { MessageBox(hWnd, L"StretchBlt has failed",L"Failed", MB_OK); goto done; } // Get the client area for size calculation RECT rcClient; GetClientRect(hWnd, &rcClient); POINT pPos; pPos.x = xSrc; pPos.y = ySrc; ClientToScreen(hWnd, &pPos);//转换坐标 //This is the best stretch mode //SetStretchBltMode(hdcWindow,HALFTONE); // Create a compatible bitmap from the Window DC hbmScreen = CreateCompatibleBitmap(hdcScreen, nWidth, nHeight); if(!hbmScreen) { MessageBox(hWnd, L"CreateCompatibleBitmap Failed",L"Failed", MB_OK); goto done; } // Select the compatible bitmap into the compatible memory DC. SelectObject(hdcMemDC,hbmScreen); // Bit block transfer into our compatible memory DC. if(!BitBlt(hdcMemDC, 0,0, nWidth, nHeight, hdcScreen, pPos.x, pPos.y, SRCCOPY)) { MessageBox(hWnd, L"BitBlt has failed", L"Failed", MB_OK); goto done; } // Get the BITMAP from the HBITMAP GetObject(hbmScreen,sizeof(BITMAP),&bmpScreen); BITMAPFILEHEADER bmfHeader; BITMAPINFOHEADER bi; bi.biSize = sizeof(BITMAPINFOHEADER); bi.biWidth = bmpScreen.bmWidth; bi.biHeight = bmpScreen.bmHeight; 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 = ((bmpScreen.bmWidth * bi.biBitCount + 31) / 32) * 4 * bmpScreen.bmHeight; if(lpvbits) { delete[] lpvbits; lpvbits = NULL; } lpvbits = new DWORD[nWidth*nHeight]; // Gets the "bits" from the bitmap and copies them into a buffer // which is pointed to by lpbitmap. GetDIBits(hdcWindow, hbmScreen, 0, (UINT)bmpScreen.bmHeight, lpvbits, (BITMAPINFO *)&bi, DIB_RGB_COLORS); //Clean up done: DeleteObject(hbmScreen); ReleaseDC(hWnd, hdcMemDC); ReleaseDC(NULL,hdcScreen); ReleaseDC(hWnd,hdcWindow); return true; }
这个是从MSDN上参考而来的。其实很简单,就是用ClientToScreen(hWnd, &pPos);把窗口中的坐标转换为对应的屏幕坐标,其他的和第一个是一样的。
附:
各图片位置,窗口大小等信息,这个找起来很麻烦。。。
m_nPicWidth = 498-1; //左右两副图本身有偏移1个像素,去掉偏移的,只比较共有的部分 m_nPicHeight = 448; m_nOffsetLeftPicX = 8; m_nOffsetLeftPicY = 193; m_nOffsetRightPicX = 516 + 1; //左右两副图本身有偏移1个像素,去掉偏移的,只比较共有的部分 m_nOffsetRightPicY = 193;