【问题标题】:In win32 programming, how do I draw a rectangle anywhere on the screen (outside my program)?在 win32 编程中,如何在屏幕上的任意位置(在我的程序之外)绘制一个矩形?
【发布时间】:2013-08-08 04:39:08
【问题描述】:

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

我不知道如何在屏幕上的任何位置绘制一个矩形,这将是截取屏幕截图的矩形区域。我已经看到其他应用程序可以做到这一点,但我找不到关于如何准确做到这一点的教程。

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

【问题讨论】:

  • 你做错了。先截图。
  • @HansPassant 然后呢?
  • 然后您可以在全屏无边框窗口中显示该位图,您可以随意绘制矩形。轻松愉快。
  • 我是这么认为的,但是如果用户改变主意要截屏怎么办?然后我会在屏幕上显示那个位图,他会认为屏幕已经冻结...
  • 如果他在你原来的场景中改变主意怎么办?同样的方式。

标签: windows winapi drawing


【解决方案1】:
  • 创建一个与屏幕大小相同的顶层窗口,并设置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,而不是 0。您的窗口看起来仍然是透明的,但会收到鼠标事件。
  • 乔纳森,它似乎不起作用。我做对了吗?我假设透明度 1 是 255(不透明)。我正在用RGB(0,0,255) 填充我的窗口,然后我打电话给SetLayeredWindowAttributes(WinHWND, RGB(0,0,255), 255, LWA_COLORKEY|LWA_ALPHA); 但是,痛苦的区域仍然让点击通过。
  • 透明度 1 是透明度 1 :) SetLayeredWindowAttributes(WinHWND, RGB(0,0,255), 1, LWA_COLORKEY); 请注意,您必须使用 LWA_COLORKEYLWA_ALPHA,不能同时使用两者。
  • 乔纳森,我试过了,但它似乎不起作用:(。我尝试了设置和不设置 LWA_ALPHA 位。(我删除了位,因为我找不到我的窗口不再,因为它是如此透明:))。我的最后一次尝试是SetLayeredWindowAttributes(WinHWND, RGB(0,0,255), 1, LWA_COLORKEY); 这将显示窗口边缘,我可以看穿填充区域,但单击透明区域仍会将鼠标带到它后面的窗口。我想知道我做错了什么。
【解决方案2】:

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

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

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

ordinal : name
100, DwmpDxGetWindowSharedSurface
101, DwmpDxUpdateWindowSharedSurface

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

如果您可以打开该句柄(您可能可以使用 D3DKMTLock 执行此操作,您应该能够在复合表面呈现到屏幕之前对其进行写入。

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

【讨论】:

  • 也许,但如果你想绘制到“真实”帧缓冲区,那么它可能是唯一的方法。
【解决方案3】:

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

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

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

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

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

祝你好运:)

【讨论】:

    猜你喜欢
    • 2014-01-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-09-30
    • 1970-01-01
    • 2021-12-16
    • 1970-01-01
    相关资源
    最近更新 更多