快速“美女找茬”(辅助工具)

简介: 最近和小徒弟玩QQ游戏中的“美女找茬”,这个游戏也就是给你两幅差不多的图片,让你找出几个不同的地方(一般是五个)。可惜我老眼昏花比较反应迟钝,总是输,被小徒弟取笑。不禁一时心血来潮,既然作为普通玩家赢不了,何不...!于是我琢磨了一下,不过就是两幅图片比较一下吗,对计算机来说当然很简单。

            最近和小徒弟玩QQ游戏中的“美女找茬”,这个游戏也就是给你两幅差不多的图片,让你找出几个不同的地方(一般是五个)。可惜我老眼昏花比较反应迟钝,总是输,被小徒弟取笑。不禁一时心血来潮,既然作为普通玩家赢不了,何不...!于是我琢磨了一下,不过就是两幅图片比较一下吗,对计算机来说当然很简单。也不需要考虑什么算法。

            所以我就做了这样一个小程序,纯属贪图好玩。我首先找到游戏窗口,然后把这个窗口“截屏”下来,在内存里判断两幅图片的不同之处,然后把结果输出到一个半透明窗口上,并且把这个半透明窗口准确的覆盖到左侧图片上。这个半透明窗口的背景是一个白色矩形,两幅图不同的地方用红色填充出来。

            判断两幅图片之前,我们做一些准备工作。先用SPY++查询出游戏窗口的窗口类和窗口标题。我在 Photoshop 里精确测量了两幅图在窗口中的起始坐标和长度宽度,很庆幸的一点是在游戏里这些值是固定的!现在为了简单起见,我们先把这些值 hard code 在程序里(如果考虑更周到,我们应该把这些信息存到一个 ini 配置文件里)。

            那么截图并且找到不同处的代码如下所示。为了考虑容错性,我还尝试了以 3*3 和 2*2 的像素矩形块为基本单位进行检测,但经过测试后我发现意义不大,实际上仅对单个像素进行检测就足够了,所以下面的代码就是逐个像素检测。

 

Code_FindDifference
//When user click the icon , then Let's GO !!! 
BOOL FindDifference(HWND hParentDlg)
{
    RECT rc, rc2;

    
//hide layer wnd
    ShowWindow(m_wndLayer, SW_HIDE);

    m_wndGame 
= FindWindow(CLASSNAME, WINDOWNAME);

    
if(m_wndGame == NULL)
    {
        MessageBox(hParentDlg, 
"没找到游戏窗口哇!还没进入房间吧?", "帮你找茬", MB_OK |MB_ICONINFORMATION);
        
return FALSE;
    }

    GetWindowRect(m_wndGame, 
&rc);
    GetWindowRect(m_wndLayer,
&rc2);
    
int width = rc.right -rc.left;
    
int height = rc.bottom - rc.top;
    
int width2 = rc2.right - rc2.left;
    
int height2 = rc2.bottom - rc2.top;

    HDC gameDC 
= GetDC(m_wndGame);
    HDC layDC 
= GetDC(m_wndLayer);

    HDC memDC 
= CreateCompatibleDC(gameDC);
    HDC memDC2
= CreateCompatibleDC(layDC);
    
    
//create game bitmap (for the first call)
    if(m_bmGame == NULL)
        m_bmGame 
= CreateCompatibleBitmap(gameDC, width, height); 
    
if(m_bmLayer == NULL)
        m_bmLayer 
= CreateCompatibleBitmap(layDC, width, height);
    
    
//把截图复制到内存DC
    HGDIOBJ hOld1 = SelectObject(memDC, m_bmGame);
    HGDIOBJ hOld2 
= SelectObject(memDC2, m_bmLayer);

    BitBlt(memDC, 
0 , 0, rc.right-rc.left, rc.bottom-rc.top, 
        gameDC, 
0, 0, SRCCOPY);


    
//把图层填充白色
    Rectangle(memDC2, 0 , 0, width2, height2);

    
//现在我们从起始点查找
    int i, j, diff;
    BYTE r1, g1, b1, r2, g2, b2;
    COLORREF color1, color2;

    
for(j = 0; j < IMG_HEIGHT; j++)
    {
        
for(i = 0; i < IMG_WIDTH; i++)
        {
            
//单像素检测
            color1 = GetPixel(memDC, IMG_X1 + i, IMG_Y + j);
            color2 
= GetPixel(memDC, IMG_X2 + i, IMG_Y + j);

            r1 
= GetRValue(color1);
            g1 
= GetGValue(color1);
            b1 
= GetBValue(color1);

            r2 
= GetRValue(color2);
            g2 
= GetGValue(color2);
            b2 
= GetBValue(color2);

            diff 
= abs(r1-r2) + abs(g1-g2) + abs(b1-b2);

            
if(diff > m_Threshold)
            {
                SetPixel(memDC2, i, j, RGB(
255, 0, 0));
            }
        }
    }
    
    SelectObject(memDC, hOld1);
    SelectObject(memDC2, hOld2);
    DeleteDC(memDC);
    DeleteDC(memDC2);

    ReleaseDC(m_wndGame, gameDC);
    ReleaseDC(m_wndLayer, layDC);

    
//把窗口移动到指定位置
    SetWindowPos(m_wndLayer, HWND_TOPMOST, 
        rc.left 
+ IMG_X1, rc.top + IMG_Y, 0, 0, 
        SWP_NOACTIVATE 
| SWP_NOSIZE | SWP_SHOWWINDOW
        );

    InvalidateRect(m_wndLayer, NULL, TRUE);
    
return TRUE;
}

 

            这个函数的效率不是很高,检测不同的时候需要等大概一秒钟的时间(我的电脑配置是 2.67GHz * 2 CPU, 3.24G 内存)。我想如果改进为直接用指针访问位图的数据块会比这种方法的效率好很多,所以它还可以改进,不过目前的处理时间对于我来说也还是可以接受的。

            然后我们需要给用户一个接口去调用上面的函数,因此我在通知栏(Tray:系统托盘)放置了一个图标,只要用户用左键单击通知栏图标,就会调用上面的函数,也就是执行一次查找,并把半透明窗口和游戏窗口进行对齐。通过鼠标右键,可以选择显示或者隐藏半透明窗口。

 

            【注意】如果游戏窗口被其他窗口遮挡,或者游戏窗口有屏幕以外,或者游戏窗口上有动态元素(例如游戏的倒计时提示等),请注意这时候获取到的窗口截图是有问题的,那么查找结果也会出现不准确的情况。 

 

             

 

            如图所示,我在左侧图上叠加了一个半透明窗口,它只是一个普通对话框(上面什么控件也没有),当然我们还需要把这个半透明窗口设置成顶层窗口,还要使其“鼠标穿透”,也就是说它自身不想接收鼠标事件,而是让鼠标“穿透”它传送给其下面的窗口,这是通过设置窗口样式来完成的。在初始化对话框时,我们用下面的代码即可:

 

Code_设置鼠标穿透
//窗口过程局部:
switch(message)
{
case WM_INITDIALOG:
    {
    
//设置图层窗口,不透明度
    SetWindowLong(hDlg, GWL_EXSTYLE, 
            GetWindowLong(hDlg, GWL_EXSTYLE) 
| 
            WS_EX_TRANSPARENT 
| //鼠标穿透
            WS_EX_LAYERED        //图层窗口
            );
    //BYTE alpha = 120;
    SetLayeredWindowAttributes(hDlg,0, 120, LWA_ALPHA);
    
return TRUE;
    }
    
break;

//其他消息。。。

}

 

 

            【注意】VC6.0提供的PLATFORM SDK(1998年的)并不支持图层窗口相关的API,因此编译时会提示找不到相应函数。要正确编译,解决方法是在 winuser.h 文件中补充相关的定义,并用高版本的Visual Studio(例如VS2003, VS2005 )中的 user32.lib 覆盖VC中的相应文件。

             我增加了一个设置对话框如下图所示,用于设置 FindDifference 函数中使用的全局参数 m_Threshold 的值。可以修改这个值的大小然后观察这个值的大小对输出结果中红色区域形状的影响,这个值越小,则输出结果的红色区域越接近为“矩形”(这个形状主要是基于游戏中使用的图片),即对差异的检验越严格。这个值越大,则输出结果的红色区域会产生收缩,使其更接近差异的“实际形状”,即使“容许误差”增加(把灰度变化较小的部分过滤,仅标示灰度差异明显的地方)。当然这个值如果设置的过大,则红色区域会减少到“完全消失”。

 

            

 

             这里是源代码的下载链接:(于 2014 年 2 月 17 日 被我撤除)

             http://files.cnblogs.com/hoodlum1980/FindIt.rar

            最后我要特别提示的是,游戏的本质是娱乐,不要为了追求浮云而失去游戏的本意。

 

    【补充】by hoodlum1980 @ 2011-11-19

    其实对于这个工具来说,仅仅是比较两幅位图,找出不同之处。其实就很简单了,我们只要两张图的位图数据块做异或就可以得到差异结果,相当于在 photoshop 中的图层模式设置为“差值”,就可以看到两个图层之间的差异。大致方法如下:

    为了加快效率,用 uint32* 类型的指针,分别指向两个位图的数据块。一般我们截图的结果是 bpp = 24,也就是说每 3 个字节为一个像素,但我们依然可以每 4 个字节一组进行异或,异或结果就是结果(同样,可以对异或结果不为0的结果像素用显著颜色标识),把结果图片呈现出来就达到和本文中相同的效果。这种方法会比我之前实现的方式速度快很多。

    但如果我们要推算出像素位置,则还需要进行一次换算,假设截图为 24 bpp,则数据排列如下:

    |   0   |    1   |   2   |

    |B G R B|G R B G|R ...

           |    0      |   1      | .....

    

    设 pDest 为结果图片的数据块指针,pSrc1和pSrc2分别是要比较的图片(注意实际上可使用同一个位图,只是水平方向上有不同偏移的定位) 

    *pDest = *pSrc1 ^ *pSrc2;

    设 uint32* p0 为数据块起始点:假设在 p1 处发现不为 0,则距离起始点的距离是:

    (p1 - p0) * sizeof(uint), 即 (p1-p0)*4;

    然后换成成像素坐标:

    stride = (width * bpp + 31)/32 * 4;

    y = (height - 1) - (p1-p0)*sizeof(uint) / stride;  //如果扫描行为逆序

    x = (((p1 - p0) * sizeof(uint)) % stride) / 3;

    注意实际上这里涵盖了两个相邻像素(x,y)和(x+1,y)组成的(由于数据块用int32对齐,所以一定在同一扫描行内),可能是 BGR|B, GR|BG, R|BGR 三种情况之一:所以我们还应该分析除以 3 以后的余数。

    令余数 k = (((p1-p0)*sizeof(uint)) % stride) % 3;

    k = 0: BGR|B   (x, y) ,     (x+1, y)的B

    k = 1: GR|BG   (x,y)的GR,   (x+1,y)的BG

    k = 2: R|BGR   (x,y)的R,    (x+1,y)

 

    如果我们需要更精确的知道到底似乎是那个像素不同导致的,我们还需要对这四个字节扫描一下,然后按照上诉情况分析不同的值位于(x,y)还是(x+1,y)。当然一般来说如果是在差异区域内部实际没必要区分。

 

    【对以上补充的补充说明:】

    由于我已经采用更佳方法(参见:http://www.cnblogs.com/hoodlum1980/p/3536444.html),所以以上补充说明已不再有效。-- hoodlum1980, on 2014-2-17。

 

 

目录
相关文章
|
监控 物联网 数据安全/隐私保护
以太网供电(POE):技术详解与应用探索
【4月更文挑战第22天】
2014 2
|
人工智能 搜索推荐 数据可视化
10个适合产品经理使用的AI生成PPT工具推荐!
AIGC时代,有哪些好用的AI生成PPT工具呢?这篇文章来给各位介绍适合产品经理使用的10款AI PPT软件!
|
缓存 Android开发
Android - 手机下载的缓存视频在文件管理怎么找不到?
Android - 手机下载的缓存视频在文件管理怎么找不到?
3556 0
Android - 手机下载的缓存视频在文件管理怎么找不到?
|
4月前
|
前端开发 数据挖掘 API
2026年6月六大主流建站工具深度横评与选型指南
2026年6月六大主流建站工具深度横评与选型指南
1436 0
|
5月前
|
数据采集 人工智能 自然语言处理
淄博企业布局GEO(AI优化):技术层面的思考框架与落地指南
本文聚焦淄博制造业、本地服务及跨境企业,解析生成式引擎优化(GEO)落地路径:破除“GEO=AI版SEO”误区,强调语义理解、结构化呈现与权威信源建设;提供“基础搭建—核心优化—迭代升级”三步技术方案,并针对地域特性给出本地语义适配、多模态呈现等实操指南,助力企业抢占AI决策链“答案主权”。
270 1
|
7月前
|
SQL 人工智能 安全
Microsoft SQL Server 2025 RTM GDR & CU3 (2026 年 3 月安全更新 | 累计更新)
Microsoft SQL Server 2025 RTM GDR & CU3 (2026 年 3 月安全更新 | 累计更新)
651 2
Microsoft SQL Server 2025 RTM GDR & CU3 (2026 年 3 月安全更新 | 累计更新)
|
9月前
|
SQL 安全 Linux
微软补丁星期二发布了 114 个漏洞及相关更新下载
微软补丁星期二发布了 114 个漏洞及相关更新下载
501 2
微软补丁星期二发布了 114 个漏洞及相关更新下载
|
安全 C++ Windows
格式化电脑和重装系统有什么区别
本文详细解析了“格式化”与“重装系统”的区别。格式化是清空磁盘数据并重建文件系统,重装系统则是重新安装操作系统,解决系统故障。两者在操作原理、影响范围和适用场景上均有不同。了解它们有助于避免误操作,并根据实际情况选择最佳处理方式。
|
监控 算法 数据挖掘
网站改版与SEO优化全流程指南(2025最新版)
本文详细梳理了网站改版的全流程,包括改版前的关键准备(现状诊断与SEO风险评估)、实施核心策略(结构优化和技术SEO要点)以及改版后的监测体系。通过数据分析工具(如Google Analytics、SEMrush)诊断现状,制定URL映射和重定向规则,优化目录结构与技术细节,并在改版后持续监控关键指标,采取补救措施,建立长效优化机制,确保流量与用户体验的稳定提升。
947 0
|
机器学习/深度学习 人工智能 自然语言处理
【人工智能】关键的人工智能领域专业术语及其简要解释
人工智能(Artificial Intelligence, AI)领域涉及众多专业术语,这些术语涵盖了从基础理论到具体应用的各个方面。以下是一些关键的人工智能领域专业术语及其简要解释
1341 2

热门文章

最新文章