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

时间:2013-08-05 23:44:00

标签: windows winapi drawing

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

我无法弄清楚的是如何在屏幕上的任何位置绘制一个矩形,这将是截取屏幕截图的矩形区域。我已经看过其他应用程序这样做,但我找不到关于如何准确执行此操作的教程。

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

3 个答案:

答案 0 :(得分: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);
    }
}

答案 1 :(得分:2)

由于现代操作系统使用合成窗口系统,因此不像过去那样“在桌面上绘图”或前端缓冲区。透明重叠可能是获得所需结果的最简单方法。

我相信有一种方法可以获得指向合成曲面的指针,但当然,您需要执行此操作的所有函数都将基本上没有文档记录。最佳起点是dwmapi.dllgdi32.dllopengl32.dll

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

ordinal : name
100, DwmpDxGetWindowSharedSurface
101, DwmpDxUpdateWindowSharedSurface

当OpenGL应用程序想要更新窗口时,SwapBuffers调用最终会通过这两个函数。分配的句柄包含所有这些中的合成表面,因为此句柄被赋予OpenGL驱动程序,以便OpenGL应用程序可以发送请求以使其帧缓冲区进入合成表面。

如果您可以打开该句柄(您可以使用D3DKMTLock执行此操作,则应该能够在合成曲面显示到屏幕之前写入。

自从我搞砸了这种事以来已经有一段时间了,但我希望这可能会指出你正确的方向。

答案 2 :(得分:1)

我有兴趣听听是否有办法在不作弊的情况下这样做...因为据我所知,所有剪辑程序都会复制当前桌面,然后才能选择要复制的区域。

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

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

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

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

祝你好运:)