Saving the contents of DirectDraw Surface

Contents

Related Articles

Introduction

Save Bitmap to File        

Capturing the Screen: Discusses the techniques of capturing the screen using GDI, DirectX, Windows Media API

Create Movie From HBitmap: Introduces the classes for generating movies (avi / wmv / quicktime) from a sequence of HBitmaps

Recording DirectX and OpenGL Animations: Introduces the classes for generating movies from DirectX and OpenGL Animations

CFugue Runtime for MIDI Score Programming: C++ Library for Programming Music Notes

Introduction

While Developing some applications like Movie Player or any simple game using DirectDraw, some times the need to provide an option for saving the screen-shot may arise. So, it is better for an application to provide such an option if it feels appropriate.

Saving the contents of the DirectDraw Surface requires the following Steps:

  1. Get Device Context (DC) of Surface which need be saved.
  2. Create Compatible Device Context to that of the Surface.
  3. Get the Size of the DirectDraw Surface.
  4. Create a bitmap compatible with the surface, with the above size.
  5. Select the bitmap object into the create Compatible Device Context.
  6. BitBlit the contents of DirectDraw Surface to the create bitmap.
  7. Now Having the bitmap ready, Open The Clipboard.
  8. Empty the Contents of Clipboard.
  9. Set the Bitmap as the content of the Clipboard.
  10. Close the Clipboard, so that it can be used by other applications.
  11. Delete the Compatible Device Context.
  12. Release the Device Context of the Direct Draw Surface.

Be careful not to delete the bitmap object which is sent to Clipboard.

These Steps Can be sublimated into piece of code as follows:

void CopySurface(IDirectDrawSurface *pDDSurface)
{ 
    HDC hdc, hmemdc ;

    HBITMAP hbitmap, hprevbitmap;

    DDSURFACEDESC ddsd;

    pDDSurface->GetDC(&hdc);

    hmemdc = CreateCompatibleDC(hdc); 

    ZeroMemory(&ddsd ,sizeof( ddsd )); // better to clear before using

    ddsd.dwSize = sizeof( ddsd ); //initialize with size 

    pDDSurface->GetSurfaceDesc(&ddsd);

    hbitmap = CreateCompatibleBitmap( hdc ,ddsd.dwWidth ,ddsd.dwHeight);

    hprevbitmap = (HBITMAP) SelectObject( hmemdc, hbitmap );

    BitBlt(hmemdc,0 ,0 ,ddsd.dwWidth ,ddsd.dwHeight ,hdc ,0 ,0,SRCCOPY);    

    if( OpenClipboard ( hWnd ) ) //hWnd == Handle to App Window 
    { 
        EmptyClipboard();
        SetClipboardData(CF_BITMAP,hbitmap);
        CloseClipboard(); 
    }
    else
        MessageBox(hWnd,"Clipboard not opened","Error",MB_OK); 

    SelectObject(hmemdc,hprevbitmap); // restore the old bitmap 

    DeleteDC(hmemdc);

    pDDSurface->ReleaseDC(hdc);

    return ; 
}

Pass the DirectDraw Surface Pointer as the parameter to this function to copy the contents to clipboard.

In case if OffScreenSurface and PrimaryScreenSurface are being used for blitting operations , and if they vary in size ,like in my MoviePlayer Application , the OffScreenSurface can be passed as parameter to copy the bitmap with original size and PrimaryScreenSurface can be Passed as parameter to copy the bitmap with enlarged size, normally the size of output.

Be Aware of the fact that this is not saving the contents of surface to the secondary storage .So, the clipboard image would be lost , if it is not saved before any new images are copied to the clipboard by any other application or the same application. Because the clipboard is unique to the entire system and works on sharing of Global Memory.

The Copied images can be saved by pasting them into new image files ,using any imaging software, like PaintBrush . This Code Comes Handy in Saving Some Random Screenshots. This can be further developed to save the surface directly to disk as bitmap file.

The following code can be used to save the bitmap onto the disk.

void SaveBitmap(char *szFilename,HBITMAP hBitmap)
{
    HDC                 hdc=NULL;
    FILE*               fp=NULL;
    LPVOID              pBuf=NULL;
    BITMAPINFO          bmpInfo;
    BITMAPFILEHEADER    bmpFileHeader; 

    do{ 

        hdc=GetDC(NULL);

        ZeroMemory(&bmpInfo,sizeof(BITMAPINFO));

        bmpInfo.bmiHeader.biSize=sizeof(BITMAPINFOHEADER);

        GetDIBits(hdc,hBitmap,0,0,NULL,&bmpInfo,DIB_RGB_COLORS); 

        if(bmpInfo.bmiHeader.biSizeImage<=0)
            bmpInfo.bmiHeader.biSizeImage=bmpInfo.bmiHeader.biWidth*abs(bmpInfo.bmiHeader.biHeight)*(bmpInfo.bmiHeader.biBitCount+7)/8;

        if((pBuf = malloc(bmpInfo.bmiHeader.biSizeImage))==NULL)
        {
            MessageBox( NULL, "Unable to Allocate Bitmap Memory", "Error", MB_OK|MB_ICONERROR);
            break;
        }            

        bmpInfo.bmiHeader.biCompression=BI_RGB;

        GetDIBits(hdc,hBitmap,0,bmpInfo.bmiHeader.biHeight,pBuf, &bmpInfo, DIB_RGB_COLORS);       

        if((fp = fopen(szFilename,"wb"))==NULL)
        {
            MessageBox( NULL, "Unable to Create Bitmap File", "Error", MB_OK|MB_ICONERROR);
            break;
        } 

        bmpFileHeader.bfReserved1=0;

        bmpFileHeader.bfReserved2=0;

        bmpFileHeader.bfSize=sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER)+bmpInfo.bmiHeader.biSizeImage;

        bmpFileHeader.bfType='MB';

        bmpFileHeader.bfOffBits=sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER); 

        fwrite(&bmpFileHeader,sizeof(BITMAPFILEHEADER),1,fp);

        fwrite(&bmpInfo.bmiHeader,sizeof(BITMAPINFOHEADER),1,fp);

        fwrite(pBuf,bmpInfo.bmiHeader.biSizeImage,1,fp); 

    }while(false); 

    if(hdc)     ReleaseDC(NULL,hdc); 

    if(pBuf)    free(pBuf); 

    if(fp)      fclose(fp);
}

The Function GetDIBits()  retrieves the bits of the specified bitmap and copies them into the specified buffer using the specified format. However, in place of the buffer if we specify NULL, the function returns the dimension and the format of bitmap in the supplied BITMAPINFO structure. In the above code we are calling the GetDIBits() function two times. In the first call we are using NULL pointer for the buffer so that we can get the dimensions of the bitmap. Once we get the size of the bitmap we allocate the memory to hold the contents of the bitmap and then call the GetDIBits() for the second time using the allocated buffer. This time GetDIBits() would retrieve the bitmap bits and would place them into the buffer which we can write into the file at the specified path using fwrite(). However, for other applications to recognize the file as a valid bitmap file we should insert proper headers at the beginning of the file. The BITMAPFILEHEADER and BITMAPINFOHEADER are the two headers that are required to be present for any valid bitmap file. Filling these headers is simple and straight forward as presented above. Once we are done with the writing we free the allocated memory using the free() function.

Note that the bitmap identified by hBitmap parameter must not be selected into a device context when the application calls this function. In case you have the bitmap selected into any device context - first unselect it before calling the above function by selecting some temporary bitmap into the device context, then call the above function and when returned from the function select it back into the device context. This way you could save any bitmap to the disk in any application.

By

P.GopalaKrishna

Homepage    Other Articles