在Windows Vista、Windows 7中,如何截取屏幕并保留应用程序区域以外的透明区域?

14
我想要截取一个应用程序的屏幕截图,并且希望矩形框中不属于应用程序区域的部分是透明的。例如,在标准的Windows应用程序中,我想要将圆角设置为透明。
我编写了一个快速的测试应用程序,它可以在XP上运行(或在关闭aero的vista/windows 7上运行)。
    protected override void OnPaint(PaintEventArgs e)
    {
        base.OnPaint(e);

        Graphics g = e.Graphics;           

        // Just find a window to test with
        IntPtr hwnd = FindWindowByCaption(IntPtr.Zero, "Calculator");

        WINDOWINFO info = new WINDOWINFO();
        info.cbSize = (uint)Marshal.SizeOf(info);
        GetWindowInfo(hwnd, ref info);


        Rectangle r = Rectangle.FromLTRB(info.rcWindow.Left, info.rcWindow.Top, info.rcWindow.Right, info.rcWindow.Bottom);
        IntPtr hrgn = CreateRectRgn(info.rcWindow.Left, info.rcWindow.Top, info.rcWindow.Right, info.rcWindow.Bottom);
        GetWindowRgn(hwnd, hrgn);

        // fill a rectangle which would be where I would probably 
        // write some mask color
        g.FillRectangle(Brushes.Red, r);

        // fill the region over the top, all I am trying to do here 
        // is show the contrast between the applications region and 
        // the rectangle that the region would be placed in
        Region region = Region.FromHrgn(hrgn);
        region.Translate(info.rcWindow.Left, info.rcWindow.Top);
        g.FillRegion(Brushes.Blue, region);
    }

当我在XP上运行这个测试应用程序(或在关闭Aero的Vista/Windows 7上),我会得到像这样的东西,这非常好,因为我可以从中提取一个异或掩码,稍后可以与BitBlt一起使用。
这里的问题是,在启用Aero的Vista或Windows 7上,窗口上不一定有区域,实际上大多数情况下都没有。有人能帮我想出在这些平台上构建类似于这样的遮罩的方法吗?
以下是我已经尝试过的一些方法...
1. 使用PrintWindow函数:这个方法行不通,因为它返回的是一个关掉Aero的窗口的屏幕截图,而带有Aero的窗口形状与之不同。
2. 使用Desktop Window Manager API获取全尺寸缩略图:这个方法也行不通,因为它直接绘制到屏幕上,从我所了解的内容来看,你无法直接从这个API中获取屏幕截图。是的,我可以打开一个粉色背景的窗口,显示缩略图,拍张屏幕截图,然后隐藏这个临时窗口,但这是一个可怕的用户体验和完全的hack,我不想把我的名字放在上面。

3. 使用 Graphics.CopyFromScreen 或其他类似的 pinvoke: 这不起作用,因为我不能假设我需要信息的窗口在屏幕的顶部。

目前,我能想到的最好的解决方案是,在 Windows 7 和 Vista 上特殊处理 Aero,手动擦掉角落,通过硬编码一些我绘制的图形路径,但这个解决方案会很糟糕,因为任何执行自定义皮肤的应用程序都会中断。

你能想到另一个或更好的解决方案吗?

如果你在这里,感谢你抽出时间阅读这篇文章,我非常感激你能提供任何帮助或指导!


有趣且深思熟虑的问题,希望它能得到更多关注。 - zildjohn01
你正在使用 GetWindowRgn() 获取窗口的区域。如果你改用 GetWindowDC()(它获取包括标题栏在内的窗口 DC),然后使用 SelectObject()GetObject() 来检索区域,会发生什么?这样做有什么不同吗?还有 GetClipRgn() 可能返回不同的形状。 - Malvineous
我还没有尝试过那种方法,但我会在这个周末尝试一下。 - Steve Sheldon
4个回答

8
如果您正在寻找一个成品应用程序,可以使用7capture,它还可以捕获半透明效果,因此图像可以保存为PNG格式以供后期合成。
编辑:
原问题和评论表明您想在Windows Vista/7上创建一个区域,然后使用该区域来遮挡捕获的图像的某些部分,就像在Windows XP和非Aero UI中所做的那样。使用区域不会给您想要的结果,因为窗口轮廓不是计算为区域,而是计算为具有可变透明度的图像-RGBA。该图像中的Alpha通道是您的掩码,但它不是像区域一样的开关掩码,而是带有从完全包含的像素到完全屏蔽的一系列值的渐进掩码。
虽然它使用了未经记录的API,但在http://spazzarama.wordpress.com/2009/02/12/screen-capture-with-vista-dwm/上的代码将捕获到RGBA缓冲区,您随后可以使用它来呈现或保存具有阴影和其他半透明效果的图像。
DwmCapture.cs中更改
BackBufferFormat = Format.X8R8G8B8

为了

BackBufferFormat = Format.A8R8G8B8

(X8->A8)

这表示将一个8位灰度值转换为带有8位alpha通道的像素。这样,您就可以从捕获的缓冲区中访问常规RGB数据和透明度。然后,将其保存为带有alpha通道的PNG或其他格式以进行合成。

谢谢你提醒我,我看到了那个应用程序,但我需要自己编写它。 - Steve Sheldon
我开始觉得这些人也作弊了,他们是通过启发式而不是程序化的方法来完成这项工作的 :) - Steve Sheldon
也许,或者他们是无情的程序员,不介意使用未发布的API。DWM确实会计算RGBA缓冲区以供后续合成使用,但似乎这个纹理并没有通过公共API提供。例如,3D任务切换就使用了它。 - mdma
我昨晚实际尝试了这段代码,它在Vista上可以运行,但在Windows 7上却不行,原因不明。 - Steve Sheldon
这是一个很好的答案,谢谢 - 但我知道不要使用未记录的API,它们之所以这样是有原因的。我在微软工作了很长时间,当他们这样做时通常都有一个很好的理由。 - Steve Sheldon

1

删除了一个在90年代可能很棒但现在看来很糟糕的想法

你说只使用DWM API只能直接捕获屏幕... 你能创建一个屏幕外的窗口(比如,X = -100000px,Y = -100000px),但是可见(甚至隐藏?)并将截图绘制到其中吗?由于使用DWM时每个窗口都有一个后备纹理,我认为即使目标不直接在屏幕上,它仍然可以正常绘制。

此外,如果您想走DirectX路线并访问实际支持窗口的DX纹理,我找到了一些可能有用的线索(特别是第一个链接):


尝试过了,不幸的是它不起作用。如上所述,当Aero被打开时,窗口的形状是不同的。 - Steve Sheldon
嘿,我看了一下示例,不幸的是这种方法有两个问题... 1. 用于获取Direct3D表面的API不受支持,所以我永远无法使用这些函数;2.(回到第1点),它们似乎在Windows 7和Vista之间发生了变化。因此,所展示的示例实际上在Windows 7上无法正常工作。不过还是感谢提醒。我希望能够直接使用DirectX7来完成某些事情... - Steve Sheldon

0
  • 使用Graphics.CopyFromScreen或其他pinvoke变体: 这种方法不起作用,因为我不能假设我需要信息的窗口在屏幕顶部的z-order。
  • 如果您知道需要哪个窗口的信息,可以将其置于前台,调用Graphics.CopyFromScreen,然后重置其z-index吗? 我从经验中知道,当项目处于后台时,Aero会执行奇怪的操作,以使其玻璃界面正常工作(部分渲染等)。 这可能不是很好的UX; 但是,它将是一种特殊情况,并且仅在启用Aero时使用。


    嘿,Nate,是的,我想过这样做,但不幸的是特殊情况很可能是默认情况,在这种情况下用户体验会很糟糕。此外,我认为Graphics.CopyFromScreen仍然没有给我区域,所以我无法将角落修整圆润。 - Steve Sheldon

    0
    你可以查看AeroShot的源代码,正如主页所描述的那样,它可以捕捉带有Aero Glass透明效果的圆角并将其保存为PNG文件。它是用C#编写的。

    它能够工作,但是他们会出现一些显著的屏幕闪烁。看起来他们首先会短暂地绘制一个已知的背景颜色,然后再将其减去。有创意。 - Steve Sheldon

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接