在win32编程中,如何在屏幕上的任何位置(程序之外)绘制矩形



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

我想不通的是,如何在屏幕上的任何地方画一个矩形,这就是要截图的矩形区域。我见过其他应用程序这样做,但我找不到关于如何做到这一点的教程。

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

  • 创建一个与屏幕大小相同的顶级窗口,并设置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);
    }
}

由于现代操作系统使用复合窗口系统,所以要像过去那样"在桌面上绘制"或前端缓冲区并不容易。透明重叠可能是实现所需结果的最简单方法。

我很有信心有一种方法可以获得指向合成曲面的指针,但当然,您需要执行的所有函数在很大程度上都是未记录的。最好的起点是dwmapi.dllgdi32.dllopengl32.dll

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

ordinal : name
100, DwmpDxGetWindowSharedSurface
101, DwmpDxUpdateWindowSharedSurface

当OpenGL应用程序想要更新窗口时,SwapBuffers调用最终会通过这两个函数。在所有这些中的某个位置都有一个包含合成曲面的分配句柄,因为该句柄被提供给OpenGL驱动程序,因此OpenGL应用程序可以发送一个请求,将其帧缓冲区闪电传输到合成曲面中。

如果你可以打开这个句柄(你可能可以用D3DKMTLock来做这件事),你应该能够在合成表面出现在屏幕上之前对其进行写入。

我已经有一段时间没有做这种事了,但我希望这能给你指明正确的方向。

我很想知道是否有办法做到这一点而不作弊。。。因为据我所知,所有的剪切程序都会在你选择要复制的区域之前复制你当前的桌面。

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

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

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

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

好运:)

最新更新