ElektroStudios ElektroStudios - 5 months ago 75
Vb.net Question

How to draw a transparent surface using SharpDX?

( This question is based on further investigations of this other question, but isn't the same question, this is very specific question about painting issues. )

I'm trying to draw a transparent surface overlapped on a target window, the problem is that I don't know how to paint it transparent, so by the moment my surface is black, and I cannot see the proper way to clear the black color of that surface in the code below.

I'd read about pixelformats and alphamodes, however, seems I cannot use the

AlphaMode.Straight
which supposedly is for allow transparency.

I'm aware of a freeware application that can do this, its name is TurboHUD (an application that draws a transparent surface on the window of a game client to draw objects, that is, a HUD). To be honest and maybe ridiculous: I'm trying to acchieve this from more than two years ago, I still don't know how to start doing this by doing the transparency I need to start drawing objects on a transparent surface.

What I'm doing wrong?. This sample code is written in VB.NET, but I accept too a solution in C#.

Imports SharpDX
Imports SharpDX.Direct2D1
Imports SharpDX.Direct3D
Imports SharpDX.DXGI
Imports SharpDX.Mathematics.Interop
Imports SharpDX.Windows

Public NotInheritable Class Form1 : Inherits Form

Private factory As New Direct2D1.Factory(Direct2D1.FactoryType.SingleThreaded)
Private render As WindowRenderTarget
Private renderProps As HwndRenderTargetProperties
Private renderThread As Thread = Nothing

Private Sub Form1_Load() Handles MyBase.Shown

Dim hwnd As IntPtr = Process.GetProcessesByName("notepad").Single().MainWindowHandle

Me.renderProps = New HwndRenderTargetProperties()
Me.renderProps.Hwnd = hwnd
Me.renderProps.PixelSize = New Size2(1920, 1080)
Me.renderProps.PresentOptions = PresentOptions.None

Me.render = New WindowRenderTarget(Me.factory, New RenderTargetProperties(New PixelFormat(Format.B8G8R8A8_UNorm, Direct2D1.AlphaMode.Premultiplied)), Me.renderProps)

Me.renderThread = New Thread(New ParameterizedThreadStart(AddressOf Me.DoRender))
Me.renderThread.Priority = ThreadPriority.Normal
Me.renderThread.IsBackground = True
Me.renderThread.Start()

End Sub

Private Sub DoRender(ByVal sender As Object)

While True
Me.render.BeginDraw()
' Me.render.Clear(New RawColor4(0, 0, 0, 0))
Me.render.Clear(SharpDX.Color.Transparent)
Me.render.Flush()
Me.render.EndDraw()
End While

End Sub

End Class


The code above is a VB.NET adaption of the accepted answer of this question.

Answer

Thanks a lot to @γηράσκω δ' αεί πολλά διδασκόμε suggestions I finally acchieved to do this using SharpDx.

The code below contains some calls to a external library, however I think the idea will be very clear.

As @γηράσκω δ' αεί πολλά διδασκόμε said, to use the WindowRenderTarget seems that I need to use it in my own form, and my form must satisfy these conditions:

  • Have a black background color.
  • Be a borderless Form.
  • Be the top-most window (obvious).
  • The window frame must be extended into the client area, by calling DwmExtendFrameIntoClientArea function.

Then I can call the method WindowRenderTarget.Clear(Color.Transparent) to make the Form transparent. Note that the Clear() method will not work for any other window than our own Form with the conditions mentioned above, this means that if we try to paint a transparent surface directlly on the target window instead of using our form to do that, we will produce a solid-color surface that can't be transparent (I really don't understand why can't.)

So after all the basic steps mentioned are done, now will be just a matter of polish things like the overlapping of the source-window on the top of the target-window (to produce the HUD effect), handle the target window resizing, and what you wish. The code below is just demonstrative, I don't handle very good those things by the moment.

Here is the code:

Imports D2D1 = SharpDX.Direct2D1
Imports D3D = SharpDX.Direct3D
Imports DXGI = SharpDX.DXGI

Imports DxColor = SharpDX.Color
Imports DxPoint = SharpDX.Point
Imports DxRectangle = SharpDX.Rectangle
Imports DxSize = SharpDX.Size2

Imports Device = SharpDX.Direct3D11.Device
Imports MapFlags = SharpDX.Direct3D11.MapFlags

Imports Elektro.Imaging.Tools
Imports Elektro.Interop.Win32
Imports Elektro.Interop.Win32.Enums
Imports Elektro.Interop.Win32.Types

Public NotInheritable Class Form1 : Inherits Form

    <DllImport("dwmapi.dll")>
    Private Shared Function DwmExtendFrameIntoClientArea(ByVal hwnd As IntPtr, ByRef margins As Margins) As Integer
    End Function

    Private factory As New D2D1.Factory(D2D1.FactoryType.SingleThreaded)
    Private render As D2D1.WindowRenderTarget
    Private renderProps As D2D1.HwndRenderTargetProperties
    Private renderThread As Thread = Nothing

    Private srcHwnd As IntPtr
    Private dstHwnd As IntPtr

    Private Sub Form1_Load() Handles MyBase.Shown

        ' Window handles of source and target window.
        Me.srcHwnd = Me.Handle
        Me.dstHwnd = Process.GetProcessesByName("notepad").Single().MainWindowHandle

        ' Form settings.
        Me.BackColor = Color.Black
        Me.FormBorderStyle = FormBorderStyle.None
        Me.TopMost = True

        ' DWM stuff for later to be able make transparent the source window.
        Dim rc As NativeRectangle ' a win32 RECT
        NativeMethods.GetClientRect(srcHwnd, rc)
        Dim margins As Margins
        margins.TopHeight = rc.Width
        margins.BottomHeight = rc.Height
        DwmExtendFrameIntoClientArea(srcHwnd, margins)
        ' ------------------------------------------------

        Me.renderProps = New D2D1.HwndRenderTargetProperties()
        Me.renderProps.Hwnd = srcHwnd
        Me.renderProps.PixelSize = New DxSize(rc.Width, rc.Height)
        Me.renderProps.PresentOptions = D2D1.PresentOptions.None

        Me.render = New D2D1.WindowRenderTarget(Me.factory, New D2D1.RenderTargetProperties(New D2D1.PixelFormat(DXGI.Format.B8G8R8A8_UNorm, D2D1.AlphaMode.Premultiplied)), Me.renderProps)

        Me.renderThread = New Thread(New ParameterizedThreadStart(AddressOf Me.DoRender))
        Me.renderThread.Priority = ThreadPriority.Normal
        Me.renderThread.IsBackground = True
        Me.renderThread.Start()

    End Sub

    Private Sub DoRender(ByVal sender As Object)

        While True
            Me.OverlapToWindow(Me.srcHwnd, Me.dstHwnd)
            Me.render.BeginDraw()
            Me.render.Clear(DxColor.Transparent)
            Me.render.Flush()
            Me.render.EndDraw()
        End While

    End Sub

    Private Sub OverlapToWindow(ByVal srcHwnd As IntPtr, ByVal dstHwnd As IntPtr)
        ' Gets the (non-client) Rectangle of the windows, taking into account a borderless window of Windows 10.
        Dim srcRect As Rectangle = ImageUtil.GetRealWindowRect(srcHwnd)
        Dim dstRect As Rectangle = ImageUtil.GetRealWindowRect(dstHwnd)

        NativeMethods.SetWindowPos(srcHwnd, dstHwnd,
                                   dstRect.X, dstRect.Y, dstRect.Top, dstRect.Left,
                                   SetWindowPosFlags.IgnoreZOrder Or SetWindowPosFlags.IgnoreResize)
    End Sub

End Class