0

我在应用程序测试团队工作。我被分配了一个任务来编写一个 win32 应用程序,它可以截取屏幕矩形部分的屏幕截图。这涉及选择要截取屏幕截图的部分屏幕。

我不知道如何在屏幕上的任何位置绘制一个矩形,这将是截取屏幕截图的矩形区域。我见过其他应用程序这样做,但我找不到有关如何准确执行此操作的教程。

我知道如何在自己的应用程序(BeginPaintRectanglewin32 函数)中绘制矩形,但是如何在屏幕上的任何位置绘制矩形?

4

3 回答 3

6
  • 创建一个与屏幕大小相同的顶层窗口,并设置WS_EX_LAYERED样式
  • 调用SetLayeredWindowAttributes并将透明颜色设置为RGB(255,0,255)
  • 用透明色完全填满你的窗口
  • 在它的顶部用另一种颜色绘制你的矩形

编辑:

这个函数用来UpdateLayeredWindow达到同样的效果。我还没有实际测试过它,但它编译好:)

void DrawRectangleOnTransparent(HWND hWnd, const RECT& rc)
{
    HDC hDC = GetDC(hWnd);
    if (hDC)
    {
        RECT rcClient;
        GetClientRect(hWnd, &rcClient);

        BITMAPINFO bmi = { 0 };
        bmi.bmiHeader.biSize = sizeof(bmi.bmiHeader);
        bmi.bmiHeader.biBitCount = 32;
        bmi.bmiHeader.biWidth = rcClient.right;
        bmi.bmiHeader.biHeight = -rcClient.bottom;

        LPVOID pBits;
        HBITMAP hBmpSource = CreateDIBSection(hDC, &bmi, DIB_RGB_COLORS, &pBits, 0, 0);
        if (hBmpSource)
        {
            HDC hDCSource = CreateCompatibleDC(hDC);
            if (hDCSource)
            {
                // fill the background in red
                HGDIOBJ hOldBmp = SelectObject(hDCSource, hBmpSource);
                HBRUSH hBsh = CreateSolidBrush(RGB(0,0,255));
                FillRect(hDCSource, &rcClient, hBsh);
                DeleteObject(hBsh);

                // draw the rectangle in black
                HGDIOBJ hOldBsh = SelectObject(hDCSource, GetStockObject(NULL_BRUSH));
                HGDIOBJ hOldPen = SelectObject(hDCSource, CreatePen(PS_SOLID, 2, RGB(0,0,0)));
                Rectangle(hDCSource, rc.left, rc.top, rc.right, rc.bottom);
                DeleteObject(SelectObject(hDCSource, hOldPen));
                SelectObject(hDCSource, hOldBsh);

                GdiFlush();

                // fix up the alpha channel
                DWORD* pPixel = reinterpret_cast<DWORD*>(pBits);
                for (int y = 0; y < rcClient.bottom; y++)
                {
                    for (int x = 0; x < rcClient.right; x++, pPixel++)
                    {
                        if ((*pPixel & 0x00ff0000) == 0x00ff0000)
                            *pPixel |= 0x01000000; // transparent
                        else
                            *pPixel |= 0xff000000; // solid
                    }
                }

                // Update the layered window
                POINT pt = { 0 };
                BLENDFUNCTION bf = { AC_SRC_OVER, 0, 255, AC_SRC_ALPHA };
                UpdateLayeredWindow(hWnd, hDC, NULL, NULL, hDCSource, &pt, 0, &bf, ULW_ALPHA);

                SelectObject(hDCSource, hOldBmp);
                DeleteDC(hDCSource);
            }
            DeleteObject(hBmpSource);
        }
        ReleaseDC(hWnd, hDC);
    }
}
于 2013-08-06T00:02:22.803 回答
2

由于现代操作系统使用复合窗口系统,因此仅“在桌面上绘图”或以前称为前端缓冲区并不容易。透明重叠可能是实现所需结果的最简单方法。

我相当有信心有一种方法可以获得指向复合表面的指针,但是当然,您需要执行此操作的所有功能在很大程度上都没有记录。最好的起点dwmapi.dllgdi32.dllopengl32.dll

首先dwmapi.dll有一些未标记的导出,但您可以获得它们的名称提示

ordinal : name
100, DwmpDxGetWindowSharedSurface
101, DwmpDxUpdateWindowSharedSurface

当 OpenGL 应用程序想要更新窗口时,SwapBuffers 调用最终会通过这两个函数。在所有这一切中都有一个包含复合表面的分配句柄,因为这个句柄被提供给 OpenGL 驱动程序,以便 OpenGL 应用程序可以发送一个请求,将其帧缓冲区 blitted 到复合表面中。

如果您可以打开该句柄(您可能可以这样做,D3DKMTLock那么您应该能够在复合表面呈现到屏幕之前对其进行写入。

我已经有一段时间没有搞砸这种事情了,但我希望这可以为你指明正确的方向。

于 2013-08-06T12:32:30.433 回答
1

我很想知道是否有办法在不作弊的情况下做到这一点......因为据我所知,所有的截图程序都会先复制您当前的桌面,然后再让您选择要复制的区域。

查看 Windows 附带的截图工具。%windir%\system32\SnippingTool.exe 它将当前桌面捕获到位图,在当前桌面上全屏显示它们,然后让您选择要复制的屏幕部分。

其他程序使用类似的技术和更高级的代码。例如,一些截图工具会在所有桌面上创建一个全屏透明窗口,然后让您在透明表单上绘制方形区域;但是,实际的图像捕获是通过移除透明窗口并将表单映射到屏幕坐标以捕获桌面的一部分来完成的。

骗局有两个原因。首先是您需要捕获鼠标输入并防止底层桌面/应用程序使用鼠标信息。其次是绘图需要画布,并且窗口不会公开包含所有桌面的单个画布。

自从我做过这样的事情以来已经有好几年了,所以也许事情已经改变了?

祝你好运 :)

于 2013-08-06T00:01:18.730 回答