0

Reproduce the problem in this project:

https://github.com/adrotter/GDIDirect2DDualRenderer


I am trying to make Direct2D work with our current rendering engine, which is GDI+. I did this by abstracting all the calls to GDI+, and making a concrete class for GDI+ and for Direct2D. One issue that a lot of people seem to have is when you call Clear on the render target with a transparent color, you get completely opaque black background.

How do I clear a Direct2D render target to fully transparent

How to create a transparent bitmap in Direct2D

The answers from the above stack overflow posts suggest I need a WS_EX_LAYERED window, or using DirectComposition, but I am not sure if doing that would work well in WinForms. Besides, they are only talking about making the window transparent and not pixels on a bitmap. The solution I came up with that partially works is if there is a call to Graphics.Clear, and I have yet to start drawing using the render target, then I call Clear on the Drawing.Graphics object, and then start drawing. This will make the background completely transparent if I use Graphics.Clear(Color.Transparent) (fixes the problem people had with the black background).

Here is how that looks like with 100% opacity:

enter image description here

The color ramp on the left is Direct2D rendering, and the color ramp on the right is GDI+. They look similar, but the Direct2D looks darker. It gets worse.

Here's 50% opacity

enter image description here

1% opacity

enter image description here

Now this is why my solution partially works: 0% opacity works great. See here.

enter image description here

Now I don't actually think that the target blending black with transparent is a result of me calling Clear first with the System.Drawing.Graphics object first, because I tested the partial transparency without using my trick (I made no calls to GDI+ graphics at all). I would still get the same results as above for opacities 50 and 1. It seems like calling DrawBitmap using a bitmap with partial transparencies will always blend the partial transparent color with black.

For reference here is how my render target is being made:

 Dim targetProperties As New RenderTargetProperties
      targetProperties.Type = RenderTargetType.Default
      targetProperties.PixelFormat = New Direct2D1.PixelFormat(Format.B8G8R8A8_UNorm, SharpDX.Direct2D1.AlphaMode.Premultiplied)
      targetProperties.Usage = RenderTargetUsage.GdiCompatible
      Dim factory = New Direct2D1.Factory()
      _target = New DeviceContextRenderTarget(factory, targetProperties)
      _target.BindDeviceContext(_graphics.GetHdc(), rec)

Also for reference here is how I turn a System.Drawing.Bitmap into a Direct2D1Bitmap:

 Private Function GetDirect2DBitmap(bmp As System.Drawing.Bitmap) As SharpDX.Direct2D1.Bitmap
    Dim bmpData As System.Drawing.Imaging.BitmapData = bmp.LockBits(
            New System.Drawing.Rectangle(0, 0, bmp.Width, bmp.Height),
            System.Drawing.Imaging.ImageLockMode.ReadOnly,
            System.Drawing.Imaging.PixelFormat.Format32bppPArgb)
    Dim stream As New SharpDX.DataStream(bmpData.Scan0, bmpData.Stride * bmpData.Height, True, False)
    Dim pFormat As New SharpDX.Direct2D1.PixelFormat(SharpDX.DXGI.Format.B8G8R8A8_UNorm, Direct2D1.AlphaMode.Premultiplied)
    Dim bmpProps As New SharpDX.Direct2D1.BitmapProperties(pFormat)

    Dim result As New SharpDX.Direct2D1.Bitmap(
            _targets(_selectedTargetIdx),
            New SharpDX.Size2(bmp.Width, bmp.Height),
            stream,
            bmpData.Stride,
            bmpProps)

    bmp.UnlockBits(bmpData)

    stream.Dispose()



    Return result
End Function

And here is a simplification of getting my semi transparent bitmaps:

//using C# style comments so this looks prettier

//Make empty bitmap to draw on
dim canvasBMP = new System.Drawing.Bitmap(100,100)

//Make the bitmap that has partial transparent pixels
dim partialTransparentBMP as System.Drawing.Bitmap = GetSemiTransparentBMP(100,100)

Using g as System.Drawing.Graphics = Graphics.FromImage(canvasBMP)
    g.Clear(Color.White)
    dim d2dTarget = MakeRenderTarget(g, g.VisibleClipBounds)
    d2dTarget.BeginDraw()
    d2dTarget.DrawBitmap(GetDirect2DBitmap(partialTransparentBMP), GetDirect2DRect(...), 1.0F, BitmapInterpolationMode.Linear)
    d2dTarget.EndDraw()
End Using

How do I get a white blended partial transparent pixel and not a black one using a GDI+ render target?

adrotter
  • 301
  • 3
  • 10
  • Just to make sure, transparent color for D2D is 0x00FFFFFF. D2D's Clear method just writes the color value (transparent or not) directly into the target. If your surface supports alpha channel, it will use what's behind accordingly. You shouldn't mix GDI and D2D on the same surface. If your engine is made for both, you don't need a GDI compatible surface. WS_EX_LAYERED is not related, it's about how your window behaves on the Windows desktop (shouldn't be used in D2D context). If you have a reproducing project, t will be easier to help – Simon Mourier Jun 11 '20 at 18:09
  • I added a reproducing project. Thanks for your help Simon. – adrotter Jun 11 '20 at 19:03
  • What happens is your mix GDI and D2D render targets and create new ones. Like I say, I think you'd better use a full D2D surface (or Direct Composition) instead of mixing the two The code would require some changes IMHO. To fix it just Clear the D2D render target with the proper color (Transparent is not ok here because it's not your surface, it's GDI+ one) before drawing on it. And you can't currently do that in the main form code because there's a bug in your logic that recreates the render target prior to calling FillRectangle. I've done a pull request in the repo – Simon Mourier Jun 12 '20 at 07:07
  • Hey Simon, your PR did make the output correct, but I wanted partialTransparentImage to have a transparent background, since this bitmap would be layered on top of another one. I am not sure if my new idea is going to work, but I was thinking of making a bitmap render target, do all the operations on that, and then copy the bitmap over to the gdi target. I'm giving that a shot, hopefully it's not a waste of time. Thanks for your help by the way. – adrotter Jun 12 '20 at 15:41
  • When using a DC render targets as you do, you render to an an internal bitmap first which is itself rendered to the DC (since you ultimately use GDI below): https://learn.microsoft.com/en-us/windows/win32/api/d2d1/nn-d2d1-id2d1dcrendertarget?redirectedfrom=MSDN#id2d1dcrendertargets--gdi-transforms--and-right-to-left-language-builds-of-windows so beyond the fact you kinda loses the benefit of D2D, I think some information is lost in translation. see also https://stackoverflow.com/a/9400311/403671 – Simon Mourier Jun 12 '20 at 16:55
  • Alright, I am dealing with my problem by drawing bitmaps that have partial transparencies using GDI+. This works OK for my situation, thanks for your help Simon – adrotter Jun 12 '20 at 18:52

0 Answers0