Converting a DirectX Surface to a GDI+ Bitmap

DirectX surfaces stored in video memory can be rendered a lot faster than images rendered through GDI+ (object oriented graphics device interface windows api).  GDI+, on the other hand, has support for saving images to a lot more formats.


I wanted to save a DirectX surface as a JPEG.  There's a Direct3D extension function that supports this; D3DXSaveSurfaceToFile().  Unfortunately it only works with surfaces that are square (width = height).  So to save a non-square JPEG I needed to convert from an IDirect3DSurface9 to a GDI+ Bitmap class then use GDI+'s encoders to save it as a JPEG.


To do this, immediately after the call to IDirect3DDevice->Present() do the following:

  • Get the current render target via GetRenderTarget(0), then get the dimensions of the render target via GetDesc().
  • Create a buffer in system memory (not video memory) to hold a copy of the current display.  This is done with CreateOffscreenPlainSurface() w/ D3DPOOL_SYSTEMMEM
  • use GetRenderTargetData() to copy (Blit) the current display to the offscreen buffer.
  • Get a pointer to the offscreen pixel buffer via LockRect().
  • Create a GDI+ Bitmap class using the constructor that takes width, height, pitch, pixel format and a pointer to the pixel buffer.

    • Use the pitch of the offscreen pixel buffer (get it via GetDesc()) not the pitch of the original surface
    • The pixel format of the bitmap must match the pixel format of the surface - this was specified when the IDirect3DDevice was created.

  • use the Bitmap->Save() (defined in Image which is inherited by Bitmap) method to write the file to a JPEG.
  • unlock the rect via UnlockRect() and release any COM interface pointers that were either directly created via QueryInterface() or were indirectly created by calling an interface returning function.

The GDI+ API docs have an excellent description of using the encoders along with a very handy GetEncoderClsid() function.

No comments:

Post a Comment