渲染半透明/透明叠加

Posted

技术标签:

【中文标题】渲染半透明/透明叠加【英文标题】:Render Translucent/Transparent Overlay 【发布时间】:2014-08-07 08:28:28 【问题描述】:

我需要一种快速的方法来在支持透明度的屏幕上绘制叠加层。我做了很多搜索,发现了一个潜在的解决方案(它有自己的问题)和另一个不符合我要求的解决方案;特别是透明度支持。

我先从后者开始,然后再谈前者。


解决方案 1

使用带有 TransparencyKey 的无边界表单,这是我发现的最推荐的解决方案之一,也是最没有帮助的解决方案。

此解决方案的工作原理是创建一个新表单,使其无边框,将背景设置为 Colour.White 并将 TransparencyKey 设置为相同的颜色,使其为全屏和最顶部,并可能设置一些其他选项让它对鼠标和键盘不可见。

这个解决方案的问题是它不支持透明度;它只会“剔除”与TransparencyKey 完全相同的颜色完全相同,因此即使是最细微的差异也会显示出在屏幕上显示半透明对象的想法。


解决方案 2

使用 P/Invoke 和 GDI+ 获取 (GetDC & Graphics.FromHdc)、实际绘制到桌面,然后释放 (ReleaseDC) 桌面。

它的功能非常好,但不幸的是存在一些问题。

首先;调用一次显然只会绘制一次,因此如果屏幕在绘制后完全刷新它会消失,我不明白如何解决这个问题,如果这是最好的解决方案,我肯定需要帮助问题。

其次; GDI+ 使用半透明画笔和FillRectangle 的方法非常慢,老实说我不能责怪它,但我要求它做得非常快; GDI+ 显然无法满足的要求。


总结;我需要一种在具有透明度支持的屏幕上绘制叠加层的方法,如果第二种方法是我应该使用的方法,我怎样才能用半透明画笔绘制得足够快,这不是问题,我将如何更新它屏幕刷新后它不会消失,如果这不是我应该使用的方法,请指定我应该使用的方法

【问题讨论】:

daniweb.com/software-development/csharp/threads/288007/… codeproject.com/Questions/197754/c-transparent-image-overlay 使用 WPF 怎么样? @WeylandYutani 你是什么意思?我对 WPF 有相当基本的了解。 WPF 是解决方案 1(全屏窗口、最顶层等)的另一种方式,但与 winforms 不同,您可以使用 alpha 组件作为窗口背景,使其完全或部分透明。此链接非常旧,但显示了可以做什么laurenlavoie.com/avalon/148。此外,您应该能够绘制精灵而不仅仅是平坦的单色背景(虽然我还没有这样做) 【参考方案1】:

解决方案不在您的列表中。解决方案#3 是使用带有 Alpha 通道的位图的 UpdateLayeredWindow。更新位图时,必须只绘制需要更新的区域,并使用最快的位图格式(预乘 ARBG)。这是我使用解决方案 #2 创建的一些图形的示例,它看起来足够快:Translucent clock and calculator

这里有更多细节。我们有一个名为 GdiBuffer 的类,它包含一个 GDI 句柄字段和一个 DC 句柄字段(都封装在 GdiHandle 和 DCHandle 类中)。初始化方式如下:

    public GdiBitmap(int width, int height)
    
        using (Bitmap bitmap = new Bitmap(width, height))
            Initialize(bitmap);
    
private void Initialize(Bitmap bitmap)
    
        _size = bitmap.Size;
        _handle = new GdiHandle(bitmap.GetHbitmap(), true);
        _deviceContext = UnsafeNativeMethods.CreateCompatibleDC(IntPtr.Zero);
        UnsafeNativeMethods.SelectObject(_deviceContext, _handle);
    

位图是 GDI+ 位图。这意味着我们的 _deviceContext 现在既代表 GDI+ 位图,又代表我们可以在 GDI 中使用的东西。

当需要更新屏幕时,我们将一个Form传递给GdiBuffer中可以更新屏幕的方法:

            if (!form.Visible)
            return false;
        IntPtr formHandle = form.Handle;
        if (formHandle == IntPtr.Zero)
            return false;
        Check.Argument(opacity >= 0 && opacity <= 1, "opacity");
        // the future bounds was stored if the TranslucentForm Bounds property
        // changed at any time. the true update to the window bounds only
        // happens here, in the UpdateLayeredWindow call
        bool futureBoundsSet = form.FutureBoundsSet;
        Rect bounds = form.GetFutureBounds();
        SIZE newSize = new SIZE(bounds.Width, bounds.Height);
        POINT newPosition = new POINT(bounds.Left, bounds.Top);
        POINT pointSource = new POINT();
        BLENDFUNCTION blend = new BLENDFUNCTION((byte)(opacity * 255));
        IntPtr screenDC = UnsafeNativeMethods.GetDC(IntPtr.Zero);

        bool result;
        try
        
            result = UnsafeNativeMethods.UpdateLayeredWindow(formHandle, screenDC, ref newPosition,
                ref newSize, _deviceContext, ref pointSource, 0, ref blend, UnsafeNativeMethods.ULW_ALPHA);
        
        finally
        
            if (screenDC != IntPtr.Zero)
                UnsafeNativeMethods.ReleaseDC(IntPtr.Zero, screenDC);
        

在内部使用我们的 GdiBuffer:

_gdiBuffer = new GdiBitmap(_bufferSize);
_graphics = Graphics.FromHdc(_gdiBuffer.DeviceContext.DangerousGetHandle());

_graphics 字段是我们实际绘制的地方。

【讨论】:

半透明画笔将覆盖从 100% 到 1% 的屏幕,我需要考虑前者。 你需要双缓冲。它内置于那种类型的窗口中,因为无论如何您都需要一个位图来更新屏幕。所以你正在渲染位图。半透明刷对我来说超级快,所以我不能说问题出在哪里。 如果你给我一个你想要的基本示例,我可以验证性能是否是测试应用程序中的问题——这种类型的事情对于 TranslucentForm 类很简单。 @Basic:不,我不是每次都创建新的位图。我只是擦除部分并每次重新绘制这些部分。只有动画的部分会被删除和重绘。这是由我们使用的矢量图形系统(VG.net)自动处理的。在您的情况下,听起来您正在使用透明的画笔进行擦除。这是预期的结果。我们对粒子系统模拟做了类似的事情,我们希望在每个粒子之后都有“轨迹”。 10K 粒子。在这种情况下,粒子系统有自己的透明位图缓冲区,作为叠加层。 @Basic:您必须使用可以擦除(替换操作)但保持像素透明的画笔进行擦除。我必须查找我们使用的确切操作,但我认为它是 Bitmap.Clear,具有透明颜色和剪切区域。

以上是关于渲染半透明/透明叠加的主要内容,如果未能解决你的问题,请参考以下文章

半透明叠加在不同的视图上显示不同的效果

在复杂场景中渲染半透明对象

叠加在图像上时,使文本显示为白色,具有半透明的黑色背景

Android:半透明覆盖?

在 OpenGL 中渲染一个*填充的*半透明立方体

半透明混合中美术术语和程序术语的区别对照表