IDirectDrawSurface saving as a bitmap
-
I have an IDirectDrawSurface and I would like to save it as a bitmap. Im only interested in 24/32 formats. I have written some code, and it exports the file correctly. It locks the surface to get the bits, and then dumps it to a file. When I view the file with MS paint, it looks fine. However, when I open in Adobe Photoshop, the colors are off (I think the blue and red are switched). It seems odd to work in MSpaint but then not in photoshop. If anybody has experience doing this, please take a look at my code. Thanks
STDMETHODIMP CCaptureWindow::SaveScreenshot(LPTSTR pFile)
{
if (!g_pCapture || !g_pBaseFilter)
return E_UNEXPECTED;CComPtr ddemv;
HRESULT hr;
if (FAILED(g_pCapture->FindInterface(&PIN_CATEGORY_PREVIEW,
&MEDIATYPE_Interleaved, g_pBaseFilter,
IID_IDDrawExclModeVideo, (void**)&ddemv)))
{
if (FAILED(hr = g_pCapture->FindInterface(&PIN_CATEGORY_PREVIEW,
&MEDIATYPE_Video, g_pBaseFilter,
IID_IDDrawExclModeVideo, (void**)&ddemv)))
{
return hr;
}
}
// ok take the screenshot now
CComPtr pSurface;
CComPtr pSurface7;
BOOL bExternal;
if (FAILED(hr=ddemv->GetDDrawSurface(&pSurface,&bExternal)))
return hr;if (FAILED(hr=pSurface->QueryInterface(IID_IDirectDrawSurface7,(void**)&pSurface7)))
return hr;DDSURFACEDESC2 desc;
desc.dwSize = sizeof(desc);
if (FAILED(hr=pSurface7->Lock(NULL,&desc,DDLOCK_WAIT | DDLOCK_READONLY,0)))
return hr;// now make sure we can save it to a file
if ((desc.ddpfPixelFormat.dwRGBBitCount < 24) || !(desc.ddpfPixelFormat.dwFlags & DDPF_RGB) || (desc.ddpfPixelFormat.dwFlags & DDPF_PALETTEINDEXEDTO8))
{
pSurface7->Unlock(NULL);
return E_UNEXPECTED;
}// now create the file
HANDLE hFile = CreateFile(pFile,GENERIC_WRITE,0,NULL,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,0);
if (hFile == INVALID_HANDLE_VALUE)
{
pSurface7->Unlock(NULL);
return GetLastError();
}CFile file(hFile);
BITMAPFILEHEADER bfh;
BITMAPINFO bmi;
DWORD sizeImage;// fill in common areas
bfh.bfType = MAKEWORD('B','M');
bfh.bfReserved1 = 0;
bfh.bfReserved2 = 0;
bfh.bfOffBits = sizeof(bmi.bmiHeader);
bmi.bmiHeader.biSize = sizeof(bmi.bmiHeader);
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biCompression = BI_RGB;
bmi.bmiHeader.biXPelsPerMeter = 0;
bmi.bmiHeader.biYPelsPerMeter = 0;
bmi.bmiHeader.biClrUsed = 0;
bmi.bmiHeader.biClrImportant = 0;
bmi.bmiHeader.biWidth = desc.dwWidth;
bmi. -
I have an IDirectDrawSurface and I would like to save it as a bitmap. Im only interested in 24/32 formats. I have written some code, and it exports the file correctly. It locks the surface to get the bits, and then dumps it to a file. When I view the file with MS paint, it looks fine. However, when I open in Adobe Photoshop, the colors are off (I think the blue and red are switched). It seems odd to work in MSpaint but then not in photoshop. If anybody has experience doing this, please take a look at my code. Thanks
STDMETHODIMP CCaptureWindow::SaveScreenshot(LPTSTR pFile)
{
if (!g_pCapture || !g_pBaseFilter)
return E_UNEXPECTED;CComPtr ddemv;
HRESULT hr;
if (FAILED(g_pCapture->FindInterface(&PIN_CATEGORY_PREVIEW,
&MEDIATYPE_Interleaved, g_pBaseFilter,
IID_IDDrawExclModeVideo, (void**)&ddemv)))
{
if (FAILED(hr = g_pCapture->FindInterface(&PIN_CATEGORY_PREVIEW,
&MEDIATYPE_Video, g_pBaseFilter,
IID_IDDrawExclModeVideo, (void**)&ddemv)))
{
return hr;
}
}
// ok take the screenshot now
CComPtr pSurface;
CComPtr pSurface7;
BOOL bExternal;
if (FAILED(hr=ddemv->GetDDrawSurface(&pSurface,&bExternal)))
return hr;if (FAILED(hr=pSurface->QueryInterface(IID_IDirectDrawSurface7,(void**)&pSurface7)))
return hr;DDSURFACEDESC2 desc;
desc.dwSize = sizeof(desc);
if (FAILED(hr=pSurface7->Lock(NULL,&desc,DDLOCK_WAIT | DDLOCK_READONLY,0)))
return hr;// now make sure we can save it to a file
if ((desc.ddpfPixelFormat.dwRGBBitCount < 24) || !(desc.ddpfPixelFormat.dwFlags & DDPF_RGB) || (desc.ddpfPixelFormat.dwFlags & DDPF_PALETTEINDEXEDTO8))
{
pSurface7->Unlock(NULL);
return E_UNEXPECTED;
}// now create the file
HANDLE hFile = CreateFile(pFile,GENERIC_WRITE,0,NULL,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,0);
if (hFile == INVALID_HANDLE_VALUE)
{
pSurface7->Unlock(NULL);
return GetLastError();
}CFile file(hFile);
BITMAPFILEHEADER bfh;
BITMAPINFO bmi;
DWORD sizeImage;// fill in common areas
bfh.bfType = MAKEWORD('B','M');
bfh.bfReserved1 = 0;
bfh.bfReserved2 = 0;
bfh.bfOffBits = sizeof(bmi.bmiHeader);
bmi.bmiHeader.biSize = sizeof(bmi.bmiHeader);
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biCompression = BI_RGB;
bmi.bmiHeader.biXPelsPerMeter = 0;
bmi.bmiHeader.biYPelsPerMeter = 0;
bmi.bmiHeader.biClrUsed = 0;
bmi.bmiHeader.biClrImportant = 0;
bmi.bmiHeader.biWidth = desc.dwWidth;
bmi.First of all, looks like 75% of your code here is useless, you don't need to copy the source surface byte by byte. DirectDraw exposes a DC. Once you've got it, you simply do a StretchBlit to a destination DC.
// m_pDDSOffscreen3 is a DirectDraw surface
HDC MemDC, SurfDC; HBITMAP hBmp,hDib; DDSURFACEDESC ddsd; LPBYTE destbuffer=NULL; BITMAP bmp; PBITMAPINFO pbmi; WORD cClrBits; LPVOID lpBits=NULL; if (m\_pDDSOffscreen3==NULL) return; // Get the size of the surface... ddsd.dwSize = sizeof(ddsd); ddsd.dwFlags = DDSD\_HEIGHT | DDSD\_WIDTH; m\_pDDSOffscreen3->GetSurfaceDesc(&ddsd); // Create the Memorybitmap now... HRESULT hr = m\_pDDSOffscreen3->GetDC(&SurfDC); if (SUCCEEDED(hr)) { // Create the Memorybitmap now... MemDC = ::CreateCompatibleDC( NULL ); hBmp = ::CreateCompatibleBitmap( SurfDC, ddsd.dwWidth, ddsd.dwHeight ); ::SelectObject( MemDC, hBmp ); ::BitBlt( MemDC, 0, 0, ddsd.dwWidth, ddsd.dwHeight, SurfDC, 0, 0, SRCCOPY ); // Bitmapstruct init... ::GetObject( hBmp, sizeof(BITMAP), (LPSTR)&bmp ); cClrBits = (WORD) 24; pbmi = (PBITMAPINFO) LocalAlloc(LPTR, sizeof(BITMAPINFOHEADER)); /\* Initialize the fields in the BITMAPINFO structure. \*/ pbmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER); pbmi->bmiHeader.biWidth = bmp.bmWidth; pbmi->bmiHeader.biHeight = bmp.bmHeight; pbmi->bmiHeader.biPlanes = bmp.bmPlanes; pbmi->bmiHeader.biBitCount = 24; if( cClrBits < 24 ) pbmi->bmiHeader.biClrUsed = 2^cClrBits; pbmi->bmiHeader.biCompression = BI\_RGB; pbmi->bmiHeader.biSizeImage = (pbmi->bmiHeader.biWidth + 7) /8 \* pbmi->bmiHeader.biHeight \* 24; pbmi->bmiHeader.biClrImportant = 0; hDib = ::CreateDIBSection(NULL,pbmi,DIB\_RGB\_COLORS,&lpBits,NULL,0); if (hDib==NULL) TRACE("CreateDIBSection failed\\n"); // Get the bits from the DC to save it to disk... GetDIBits( SurfDC, hBmp, 0, (WORD) bmp.bmHeight, lpBits, pbmi, 0 ); if( lpBits == NULL ) TRACE( "No memory for screenshot!\\n" ); if (bAddTail) { CString name=\_T(""); if (GetDocument()->addShot(name,nStart,nEnd) && pThumb->AddItem(CBitmap::FromHandle(hDib), name)) { GetDocument()->addShotInfo(nStart,nEnd, pThumb->GetImageListIndex(),pThumb->GetItemIndex()); } } else { // remplace replaceShot(CBitmap::FromHandle(hDib)); } m\_pDDSOffscreen3->ReleaseDC(SurfDC ); ::SelectObject(MemDC,NULL); ::DeleteDC(MemDC); ::DeleteObject(hBmp); :
-
First of all, looks like 75% of your code here is useless, you don't need to copy the source surface byte by byte. DirectDraw exposes a DC. Once you've got it, you simply do a StretchBlit to a destination DC.
// m_pDDSOffscreen3 is a DirectDraw surface
HDC MemDC, SurfDC; HBITMAP hBmp,hDib; DDSURFACEDESC ddsd; LPBYTE destbuffer=NULL; BITMAP bmp; PBITMAPINFO pbmi; WORD cClrBits; LPVOID lpBits=NULL; if (m\_pDDSOffscreen3==NULL) return; // Get the size of the surface... ddsd.dwSize = sizeof(ddsd); ddsd.dwFlags = DDSD\_HEIGHT | DDSD\_WIDTH; m\_pDDSOffscreen3->GetSurfaceDesc(&ddsd); // Create the Memorybitmap now... HRESULT hr = m\_pDDSOffscreen3->GetDC(&SurfDC); if (SUCCEEDED(hr)) { // Create the Memorybitmap now... MemDC = ::CreateCompatibleDC( NULL ); hBmp = ::CreateCompatibleBitmap( SurfDC, ddsd.dwWidth, ddsd.dwHeight ); ::SelectObject( MemDC, hBmp ); ::BitBlt( MemDC, 0, 0, ddsd.dwWidth, ddsd.dwHeight, SurfDC, 0, 0, SRCCOPY ); // Bitmapstruct init... ::GetObject( hBmp, sizeof(BITMAP), (LPSTR)&bmp ); cClrBits = (WORD) 24; pbmi = (PBITMAPINFO) LocalAlloc(LPTR, sizeof(BITMAPINFOHEADER)); /\* Initialize the fields in the BITMAPINFO structure. \*/ pbmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER); pbmi->bmiHeader.biWidth = bmp.bmWidth; pbmi->bmiHeader.biHeight = bmp.bmHeight; pbmi->bmiHeader.biPlanes = bmp.bmPlanes; pbmi->bmiHeader.biBitCount = 24; if( cClrBits < 24 ) pbmi->bmiHeader.biClrUsed = 2^cClrBits; pbmi->bmiHeader.biCompression = BI\_RGB; pbmi->bmiHeader.biSizeImage = (pbmi->bmiHeader.biWidth + 7) /8 \* pbmi->bmiHeader.biHeight \* 24; pbmi->bmiHeader.biClrImportant = 0; hDib = ::CreateDIBSection(NULL,pbmi,DIB\_RGB\_COLORS,&lpBits,NULL,0); if (hDib==NULL) TRACE("CreateDIBSection failed\\n"); // Get the bits from the DC to save it to disk... GetDIBits( SurfDC, hBmp, 0, (WORD) bmp.bmHeight, lpBits, pbmi, 0 ); if( lpBits == NULL ) TRACE( "No memory for screenshot!\\n" ); if (bAddTail) { CString name=\_T(""); if (GetDocument()->addShot(name,nStart,nEnd) && pThumb->AddItem(CBitmap::FromHandle(hDib), name)) { GetDocument()->addShotInfo(nStart,nEnd, pThumb->GetImageListIndex(),pThumb->GetItemIndex()); } } else { // remplace replaceShot(CBitmap::FromHandle(hDib)); } m\_pDDSOffscreen3->ReleaseDC(SurfDC ); ::SelectObject(MemDC,NULL); ::DeleteDC(MemDC); ::DeleteObject(hBmp); :
Thanks for the tip, but I think your solution seems slightly wasteful. You suggest creating a DIB from a DDB which requires allocating a new DDB + memory for the DIB bits. Isn't Lock'ing the surface the same thing as getting the DIB bits (and hence you can avoid the DDB HBITMAP stuff)?
-
Thanks for the tip, but I think your solution seems slightly wasteful. You suggest creating a DIB from a DDB which requires allocating a new DDB + memory for the DIB bits. Isn't Lock'ing the surface the same thing as getting the DIB bits (and hence you can avoid the DDB HBITMAP stuff)?
Todd Jeffreys wrote: You suggest creating a DIB from a DDB Yes? That is AFAIK what the very definition of a .BMP-file says it contains.