Render Translucent/Transparent Overlay

2019-04-06 17:43发布

I need a fast way to draw an overlay on a screen with transparency support. I've done a lot of searching and found one potential solution (which has its own problems) and another solution that does not fit my requirements; specifically transparency support.

I'll start with the latter and then touch on the former.


Solution 1

Using a borderless form with a TransparencyKey, this is one of the most recommended solutions I've found and the least helpful.

This solution works by having a new Form, have it borderless, set the background to something like Colour.White and set the TransparencyKey to the same colour, have it be fullscreen and topmost, and maybe set some other options to have it act invisible to the mouse and keyboard.

The problem with this solution is that it does not support transparency; it will only 'knock out' colours exactly the same as the TransparencyKey and as such anything with even the slightest difference will be shown throwing the idea of having translucent objects on the screen out the window.


Solution 2

Use P/Invoke and GDI+ to get (GetDC & Graphics.FromHdc), actually draw to, and then release (ReleaseDC) the desktop.

For what it does it works quite well, unfortunately however there are some problems.

First of all; calling it once will quite obviously only draw it once and as such if the screen refreshes at all after being drawn it will disappear, I do not understand how to fix this and if this is the best solution I'll most definitely need help with this issue.

Second of all; GDI+ is insanely slow with this method using a translucent Brush and FillRectangle and I honestly can't blame it, however I require it to be done quite fast; a requirement which GDI+ apparently cannot fufill.


In conclusion; I require a method of drawing an overlay over a screen that has transparency support, if the second method is the method I should be using how can I draw with a translucent brush fast enough that it's not a problem and how would I have it update so that it wont disappear after the screen refreshes, and if it's not the method I should be using please specify what method I should be using.

1条回答
太酷不给撩
2楼-- · 2019-04-06 18:36

The solution is not in your list. Solution #3 is to use UpdateLayeredWindow with a bitmap with an alpha channel. When updating the bitmap, you must paint only the areas that need updating, and use the fastest bitmap format (pre-multiplied ARBG). Here is an example of some graphics I created using solution #2, which seems fast enough: Translucent clock and calculator

Here are some more details. We have a class called GdiBuffer, containing a GDI handle field and a DC handle field (both encapsulated in classes, GdiHandle and DCHandle). It is initialized in the following way:

    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);
    }

The Bitmap is a GDI+ bitmap. This means we _deviceContext is now representing both the GDI+ bitmap, and something we can use in GDI.

When it is time to update the screen, we pass a Form into the method in GdiBuffer that can update the screen:

            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);
        }

To use our GdiBuffer internally:

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

The _graphics field is where we actually draw.

查看更多
登录 后发表回答