How to draw a transparent surface using SharpDX?
Asked Answered
E

1

10

( 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.

Episcopalism answered 23/6, 2016 at 12:7 Comment(13)
Here's a link worth looking at - #26647215Behest
Thanks for comment and for trying to help. I also found that question but I think (I think) that is not applicable to this scenario, because that is for making transparent a shape/object, not a surface. Honestlly I don't know how to test/reproduce that example.Episcopalism
Looking at this MSDN page it seems that the Straight alpha mode is not supported when dealing with the ID2D1HwndRenderTarget interface (which I guess SharpDX is using).Snowbird
For transparency to work, as I wrote in my answer, you need to extend the aero glass to cover all the form DwmExtendFrameIntoClientArea and use black as the background color of your form. This works with DirectX so it will work with SharpDX also.Overabound
@γηράσκω δ' αεί πολλά διδασκόμε Once you knew DirectX, surelly you could have much more idea than me using SharpDx; maybe I did something wrong but I used DwmExtendFrameIntoClientArea with the proper size in the corresponding margins struct and I still see a black background on the target window. The kind of hwnd render-target I'm using seems does not have an option/property to set the default color ...or I can't find it, so I can't verify the color is black, but I'm trying to "clear" black so I see a black surface.Episcopalism
@Visual Vincent I see.. but then?.I'm very confussed because if that limitation means that I must use interface ID2D1BitmapRenderTarget or ID2D1RenderTarget instead,then the constructors of the SharpDX's wrapper class (BitmapRenderTargetandWicRenderTarget I supose) takes as parameter another (base)RenderTarget class!!,so I'm trying to instantiate a class that inherits from RenderTarget but its constructor asks me for another RenderTarget instance? I don't understand it;also I can pass a "native pointer" but I don't know what it means in this DirectX meaning.I'm totally lost.Episcopalism
@γηράσκω δ' αεί πολλά διδασκόμε I'm not using my Form to positionate it over the target window, that is managed by the HwndRenderTarget at all (from what I think I understood). The reason why I would like to change from XNA to Sharp DX is because XNA is deprecated while on the other hand SharpDX its in continued development; and also to avoid Win32 window hacks about DWM/GDI/Transparency that will negativelly impact the overall performance of my app, because I supose that If I could reproduce the same only using DirectX then It will be more faster and efficient.Episcopalism
A native pointer is just a pointer to a ID2D1HwndRenderTarget instance. In VB.NET/C# this will be an IntPtr.Snowbird
You can not draw transparently on a different window (i saw that in your code). You need a transparent form(window) on top of the game. It says it very clearly in TurboHUD page: "paint on a transparent Direct2D window (it's not working in "Fullscreen" mode, only in "Windowed" and "Windowed (Fullscreen)")"Overabound
Make your form top most and use your forms handle not notepad ones. Remember, your form must not have borders. PS i too find sharpDX difficult to understand. DirectX is very easy for basic things if of cource you are familiar with C C++ syntaxOverabound
@γηράσκω δ' αεί πολλά διδασκόμε Thanks for comment, then I will try putting a form in top-most and see if I finally acchieve this (I mean using SharpDX). I'm not familiar with C/C++ languages, but don't have many problems to understand a code with a (non-advanced) syntax usage.Episcopalism
Have you find your way with sharpDX? If not i suggest you use GDI. If you are not going to draw thousand of lines or hundred of large images then GDI is fast enough to not have an impact to the performance.Overabound
@γηράσκω δ' αεί πολλά διδασκόμε Yes I finally solved it (better said you solved it!!). Maybe you could see the code I published in my answer? I would like to ensure that I'm doing things on the right side without loosing performance. If at any moment you want to publish an answer to this thread with a code more optimized (In C# or VB.NET, no matter), I will accept the answer.Episcopalism
E
3

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
Episcopalism answered 8/7, 2016 at 1:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.