Search code examples
c#winformssharpdx

Converting System.drawing to SharpDX.direct 2D1


I am working on a graphics application that can draw on the CPU or the GPU depending on users setting. To draw on CPU, I am using GDI+ technologies with System.Drawing component. To draw on GPU, I want to use SharpDX.Direct2D1 (because I am using c#).

I made a DrawingContext abstract class which implements every function of drawing of Graphics class(receiving System.Drawing.Brush, System.Drawing.Rectangle, etc... as parameters) and reimplements them into it's derived class (CPUDrawingContext). Now, I have a GPUDrawingContext class that has to overrides all of these methods, but since the parameters are from type System.Drawing, I need to convert them into sharpDX component, very fast so we can't see the difference.

See a little example here : i've only put some portion of code so you can see the concept.

DrawingContext

Public abstract class DrawingContext {
//System.drawing.Bitmap, System.Drawing.RectangleF
Public asbstract DrawImage(Bitmap b, RectangleF dest, RectangleF source,GraphicsUnit g);
//System.Drawing.Brush
Public abstract DrawRectangle(Brush b, Rectangle rect);


}

CPUDrawingContext: using graphicContext As Graphics to render on screen

Public  class CPUDrawingContext{
  //System.drawing.Bitmap, System.Drawing.RectangleF
  Public override DrawImage(Bitmap b, RectangleF dest, RectangleF source,GraphicsUnit g);
  //System.Drawing.Brush
  Public override DrawRectangle(Brush b, Rectangle rect){
      graphicContext.Rectangle(b,rect);
  }
  Public override DrawImage(Bitmap b, RectangleF dest, RectangleF source, GraphicsUnit g) {
      graphicContext.DrawImage(b,dest,source,g);
  }
}

GPUDrawingContext : using renderTarger As RenderTarget to render on screen

Public class GPUDrawingContext {
   //System.drawing.Bitmap, System.Drawing.RectangleF
  Public override DrawImage(Bitmap b, RectangleF dest, RectangleF  source,GraphicsUnit g);
  //System.Drawing.Brush
  Public override DrawRectangle(Brush b, Rectangle rect){
      //convert b and rect to fit sharpDX component
      renderTarget.DrawRectangle(b,rect);
   }
  Public override DrawImage(Bitmap b, RectangleF dest, RectangleF source, GraphicsUnit g) {
      //(convert b,dest,source and g to fit sharpDX component)
      renderTarget.DrawImage(b,dest,source,g);
   }
 }

I have commented into GPUDrawingContext the area that I need to convert before drawing.

My question is, is it possible to do that very fast so we won't notice (like conversion of less than 10ms).

Since my application will need to draw Bitmap, I need to convert System.Drawing.Bitmap into SharpDX.Direct2D1.Bitmap very fast but i've noticed sharpDX bitmap does not seem really compatible with System.Drawing.Bitmap.


Solution

  • So this is how I proceed to convert a System.Drawing.Bitmap to a SharpDX.Direct2d1.Bitmap.

    Dim bmpData As BitmapData = m_Bitmap.LockBits(New System.Drawing.Rectangle(0, 0, m_Bitmap.Width, m_Bitmap.Height), System.Drawing.Imaging.ImageLockMode.ReadOnly, m_Bitmap.PixelFormat)
    
      Dim bgraArray As Byte() = New Byte(bmpData.Width * bmpData.Height * 4 - 1) {}
    
      Dim scan As IntPtr = bmpData.Scan0
      Marshal.Copy(scan, bgraArray, 0, (bmpData.Width * bmpData.Height) * 4)
    
      Dim rgbaarray As Byte() = New Byte(bmpData.Width * bmpData.Height * 4 - 1) {}
    
      Parallel.For(0, 4, Sub(range)
                            For i As Integer = range * bmpData.Height / 4 To bmpData.Height / 4 * (range + 1) - 1
                               Dim offset As Integer = i * bmpData.Width * 4
                               For j As Integer = 0 To bmpData.Width * 4 - 1 Step 4
                                  rgbaarray(offset + j) = bgraArray(offset + j + 2)
                                  rgbaarray(offset + j + 1) = bgraArray(offset + j + 1)
                                  rgbaarray(offset + j + 2) = bgraArray(offset + j)
                                  rgbaarray(offset + j + 3) = bgraArray(offset + j + 3)
                               Next
                            Next
                         End Sub)
    
      Dim stream As DataStream = New DataStream(bmpData.Height * bmpData.Width * 4, True, True)
      stream.WriteRange(rgbaarray)
      stream.Position = 0
      m_sharpDXbitmap = New SharpDX.Direct2D1.Bitmap(deviceContext, New Size2(m_Bitmap.Width, m_Bitmap.Height), stream, bmpData.Width * 4, bitmapProperties)
      m_Bitmap.UnlockBits(bmpData)
    
      stream.Dispose()
    

    However, this is not taking 10ms but is taking 120ms. What I did to keep the abstraction that I wanted is to create a CustomBitmap class that contains both bitmaps, and on the first draw call, if it has not been converted yet, convert it only once, so on futur draw calls, this is going to be instant. Here is my CustomBitmap class :

    Imports System.Drawing.Imaging
    Imports System.Reflection
    Imports SharpDX
    Imports SharpDX.Direct2D1
    Imports System.Runtime.InteropServices
    Imports Synergx.Common.Drawing
    Imports System.Collections.Concurrent
    Imports System.Threading.Tasks
    
    Public Class CustomBitmap
    
       Private bitmapProperties As BitmapProperties = New BitmapProperties(New SharpDX.Direct2D1.PixelFormat(SharpDX.DXGI.Format.R8G8B8A8_UNorm, SharpDX.Direct2D1.AlphaMode.Ignore))
    
       Public Property Bitmap As System.Drawing.Bitmap
          Get
             Return m_Bitmap
          End Get
          Set(value As System.Drawing.Bitmap)
             m_Bitmap = value
          End Set
       End Property
    
       Private m_Width As Integer
       Private m_Height As Integer
       Private m_Bitmap As System.Drawing.Bitmap
       Private m_sharpDXbitmap As SharpDX.Direct2D1.Bitmap = Nothing
    
       Public ReadOnly Property Width As Integer
          Get
             Return m_Width
          End Get
       End Property
    
       Public ReadOnly Property Height As Integer
          Get
             Return m_Height
          End Get
       End Property
    
       Public ReadOnly Property GPUBitmap As SharpDX.Direct2D1.Bitmap
          Get
             Return m_sharpDXbitmap
          End Get
       End Property
    
       Public Sub New(bitmap As System.Drawing.Bitmap)
          Me.Bitmap = bitmap
          m_Width = bitmap.Width
          m_Height = bitmap.Height
       End Sub
    
       Friend Sub GenerateSharpDXBitmap(deviceContext As SharpDX.Direct2D1.DeviceContext)
    
          If (m_Bitmap.PixelFormat = Imaging.PixelFormat.Format8bppIndexed) Then
             GenerateSharpDXBitmap8bpp(deviceContext)
          ElseIf (m_Bitmap.PixelFormat = Imaging.PixelFormat.Format32bppArgb) Then
             GenerateSharpDXBitmap32argp(deviceContext)
          End If
    
       End Sub
    
       Private Sub GenerateSharpDXBitmap8bpp(deviceContext As SharpDX.Direct2D1.DeviceContext)
    
          Dim bmpData As BitmapData = m_Bitmap.LockBits(New System.Drawing.Rectangle(0, 0, m_Bitmap.Width, m_Bitmap.Height), System.Drawing.Imaging.ImageLockMode.ReadOnly, m_Bitmap.PixelFormat)
    
          Dim integerArray As Integer()
          ReDim integerArray(bmpData.Height * bmpData.Width - 1)
    
          Parallel.For(0, 4, Sub(range)
                                Dim scan As IntPtr = bmpData.Scan0
    
                                For i As Integer = 0 To bmpData.Height * range / 4 - 1 - 1
                                   scan += bmpData.Stride
                                Next
    
                                For y As Integer = bmpData.Height * range / 4 To bmpData.Height * (range + 1) / 4 - 1
                                   Dim bytes As Byte() = New Byte(bmpData.Width - 1) {}
                                   Marshal.Copy(scan, bytes, 0, bmpData.Width)
    
                                   For x As Integer = 0 To bytes.Length - 1
                                      Dim B As Integer = bytes(x)
                                      Dim rgba As Integer = B Or (B << 8) Or (B << 16) Or (B << 24)
                                      integerArray(x + y * bmpData.Width) = rgba
                                   Next
                                   scan += bmpData.Stride
    
                                Next
                             End Sub)
    
          Dim stream As DataStream = New DataStream(bmpData.Height * bmpData.Width * 4, True, True)
          stream.WriteRange(integerArray)
          stream.Position = 0
          m_sharpDXbitmap = New SharpDX.Direct2D1.Bitmap(deviceContext, New Size2(m_Bitmap.Width, m_Bitmap.Height), stream, bmpData.Width * 4, bitmapProperties)
          m_Bitmap.UnlockBits(bmpData)
    
          stream.Dispose()
    
       End Sub
    
       Private Sub GenerateSharpDXBitmap32argp(deviceContext As SharpDX.Direct2D1.DeviceContext)
          Dim bmpData As BitmapData = m_Bitmap.LockBits(New System.Drawing.Rectangle(0, 0, m_Bitmap.Width, m_Bitmap.Height), System.Drawing.Imaging.ImageLockMode.ReadOnly, m_Bitmap.PixelFormat)
    
          Dim bgraArray As Byte() = New Byte(bmpData.Width * bmpData.Height * 4 - 1) {}
    
          Dim scan As IntPtr = bmpData.Scan0
          Marshal.Copy(scan, bgraArray, 0, (bmpData.Width * bmpData.Height) * 4)
    
          Dim rgbaarray As Byte() = New Byte(bmpData.Width * bmpData.Height * 4 - 1) {}
    
          Parallel.For(0, 4, Sub(range)
                                For i As Integer = range * bmpData.Height / 4 To bmpData.Height / 4 * (range + 1) - 1
                                   Dim offset As Integer = i * bmpData.Width * 4
                                   For j As Integer = 0 To bmpData.Width * 4 - 1 Step 4
                                      rgbaarray(offset + j) = bgraArray(offset + j + 2)
                                      rgbaarray(offset + j + 1) = bgraArray(offset + j + 1)
                                      rgbaarray(offset + j + 2) = bgraArray(offset + j)
                                      rgbaarray(offset + j + 3) = bgraArray(offset + j + 3)
                                   Next
                                Next
                             End Sub)
    
          Dim stream As DataStream = New DataStream(bmpData.Height * bmpData.Width * 4, True, True)
          stream.WriteRange(rgbaarray)
          stream.Position = 0
          m_sharpDXbitmap = New SharpDX.Direct2D1.Bitmap(deviceContext, New Size2(m_Bitmap.Width, m_Bitmap.Height), stream, bmpData.Width * 4, bitmapProperties)
          m_Bitmap.UnlockBits(bmpData)
    
          stream.Dispose()
       End Sub
    
       Public Sub Dispose()
          If Bitmap IsNot Nothing Then
             m_Bitmap.Dispose()
             m_Bitmap = Nothing
          End If
          If m_sharpDXbitmap IsNot Nothing Then
             m_sharpDXbitmap.Dispose()
             m_sharpDXbitmap = Nothing
          End If
       End Sub
    
    End Class
    

    The class contains 2 different methods that can separe to grayscale colors or to 32bpp depending on the original PixelFormat of your image.