_VIDEO FOR WINDOWS AND WING_ by Christopher Kelly Listing One #define STRICT #include #include #include static HINSTANCE hInstanceG = 0; // Data instance handle. static HWND hMCIWndG = 0 ; // Handle of the MCI display window. // Function prototypes static void ResizeWindowToFit(HWND hWnd); // Make DlgProc extern "C" to prevent C++ name mangling. extern "C" BOOL CALLBACK DlgProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInst, LPSTR pCmdLine, int cmdShow) { return DialogBox(hInstanceG = hInstance,"AVISEE",0,DlgProc); } // Dialog Procedure BOOL CALLBACK DlgProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch(msg) { case WM_INITDIALOG: hMCIWndG = MCIWndCreate(hWnd,hInstanceG, WS_CHILD | WS_VISIBLE | MCIWNDF_NOTIFYSIZE,0); ResizeWindowToFit(hWnd); return TRUE; case WM_CLOSE: EndDialog(hWnd,0); return TRUE; case WM_PALETTECHANGED: case WM_QUERYNEWPALETTE: SendMessage(hMCIWndG,msg,wParam,lParam); return TRUE; case MCIWNDM_NOTIFYSIZE: ResizeWindowToFit(hWnd); return TRUE; } return FALSE; } static void ResizeWindowToFit(HWND hWnd) { RECT rect; GetWindowRect(hMCIWndG,&rect); AdjustWindowRect(&rect,GetWindowLong(hWnd,GWL_STYLE),FALSE); SetWindowPos(hWnd,0,0,0,rect.right-rect.left,rect.bottom-rect.top, SWP_NOMOVE | SWP_NOZORDER); } Listing Two #define STRICT #include #include #include #include #include #include #include #include // Global Variables static HINSTANCE hInstanceG = 0; // Data instance handle. static HWND hMCIWndG = 0 ; // Handle of the MCI display window. static FARPROC pDrawHandlerThunkG=0; // Instance thunk for draw handler. // Private data structure used for storing drawing information. // This is C++ so it can have methods. struct DrawInfo { // Methods DrawInfo(); ~DrawInfo(); LRESULT Begin(ICDRAWBEGIN FAR *pBegin); LRESULT Draw(ICDRAW FAR *pDrawStruct); LRESULT End(); LRESULT ChangePalette(LPBITMAPINFOHEADER pInfoHeader); LRESULT GetPalette(); LRESULT Realize(HDC hDC, BOOL background); BOOL CanHandleFormat(LPBITMAPINFOHEADER pInfoHeader); void ComposeFrame(LPBITMAPINFOHEADER pInfoHeader, LPVOID pImageBits); LRESULT SuggestFormat(ICDRAWSUGGEST FAR *pSuggest); // Data members LPVOID pBuffer_; HDRAWDIB hDD_; HDC hDC_; HDC hWinGDC_; HBITMAP hWinGBitmap_; HBITMAP hOldBitmap_; int xDst_; // Destination rectangle int yDst_; int dxDst_; int dyDst_; int xSrc_; // Source rectangle int ySrc_; int dxSrc_; int dySrc_; char aCaption_[32]; // Text to write int captionX_; // Current position of int captionY_; // the text on the window. int windowWidth_; // Width of the video window. } ; // Function prototypes // Make exported functions extern "C" to prevent C++ name mangling. extern "C" { BOOL CALLBACK DlgProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); LRESULT CALLBACK DrawHandler(DWORD id, HDRVR hDriver, UINT MSG, LPARAM lParam1, LPARAM lParam2); } static void ResizeWindowToFit(HWND hWnd); static void CopySystemPalette(LPRGBQUAD pColors); static BOOL Is256ColorDisplay(); static BOOL InstallDrawHandler(HWND hMCIWnd); static LRESULT HandleDriverOpen(ICOPEN FAR *pOp); static LRESULT HandleDriverClose(DrawInfo *pDraw); int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInst, LPSTR pCmdLine, int cmdShow) { if(Is256ColorDisplay()) DialogBox(hInstanceG = hInstance,"AVISEE",0,DlgProc); else MessageBox(0,"This program requires a 256 color display", "AVISEE",MB_OK); return 0; } // Dialog Procedure BOOL CALLBACK DlgProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch(msg) { case WM_INITDIALOG: // Create the video window. hMCIWndG = MCIWndCreate(hWnd,hInstanceG, WS_CHILD | WS_VISIBLE | MCIWNDF_NOTIFYSIZE | MCIWNDF_NOTIFYMEDIA,0); ResizeWindowToFit(hWnd); return TRUE; case WM_CLOSE: EndDialog(hWnd,0); return TRUE; case WM_PALETTECHANGED: case WM_QUERYNEWPALETTE: // Pass on palette messages. SendMessage(hMCIWndG,msg,wParam,lParam); return TRUE; case MCIWNDM_NOTIFYSIZE: ResizeWindowToFit(hWnd); return TRUE; case MCIWNDM_NOTIFYMEDIA: InstallDrawHandler((HWND)wParam); return TRUE; } return FALSE; } static void ResizeWindowToFit(HWND hWnd) { RECT rect; GetWindowRect(hMCIWndG,&rect); AdjustWindowRect(&rect,GetWindowLong(hWnd,GWL_STYLE),FALSE); SetWindowPos(hWnd,0,0,0,rect.right-rect.left,rect.bottom-rect.top, SWP_NOMOVE | SWP_NOZORDER); } static void CopySystemPalette(LPRGBQUAD pColors) { PALETTEENTRY aPal[256]; HDC hDC = GetDC(0); GetSystemPaletteEntries(hDC,0,256,aPal); // Unfortuanately RGBQUAD and PALETTEENTRY have the colors in the // opposite order so we have to copy them one by one. for(int i=0; i<256; i++) { pColors[i].rgbRed = aPal[i].peRed; pColors[i].rgbGreen = aPal[i].peGreen; pColors[i].rgbBlue = aPal[i].peBlue; pColors[i].rgbReserved = 0; } ReleaseDC(0,hDC); } static BOOL Is256ColorDisplay() { BOOL ok = TRUE; HDC hDC = GetDC(0); // Get DC for desktop window. // Check it is a palettized display. if(GetDeviceCaps(hDC,RASTERCAPS) & RC_PALETTE==0) ok = FALSE; // Check it is 256 colors (8 bits per pixel). if(GetDeviceCaps(hDC,BITSPIXEL)*GetDeviceCaps(hDC,PLANES)!=8) ok = FALSE; ReleaseDC(0,hDC); return ok; } static BOOL InstallDrawHandler(HWND hMCIWnd) { BOOL ok = TRUE; MCI_DGV_SETVIDEO_PARMS parms; // We may be called before we MCIWndCreate has returned and so the // MCI window handler will not have been assigned to hMCIWndG. if(!hMCIWndG) hMCIWndG = hMCIWnd; // If we haven't create the instance thunk then do so. if (!pDrawHandlerThunkG) pDrawHandlerThunkG = MakeProcInstance((FARPROC)DrawHandler,hInstanceG); parms.dwValue = (DWORD)pDrawHandlerThunkG; parms.dwItem = MCI_AVI_SETVIDEO_DRAW_PROCEDURE; // MCIWnd does not provide a function for installing a draw handler // so we get the MCI device ID and set it the MCI_SETVIDEO window. UINT deviceID = MCIWndGetDeviceID(hMCIWndG); if(deviceID) { mciSendCommand(deviceID,MCI_SETVIDEO, MCI_DGV_SETVIDEO_ITEM | MCI_DGV_SETVIDEO_VALUE, (DWORD) (MCI_DGV_SETVIDEO_PARMS FAR*)&parms); } return ok; } // The Draw Handler LRESULT CALLBACK __export DrawHandler(DWORD id, HDRVR hDriver, UINT msg, LPARAM lParam1, LPARAM lParam2) { DrawInfo *pDraw = (DrawInfo*)id; switch (msg) { // Many of the driver messages require no processing so we // will get them out of the way first. case DRV_LOAD: case DRV_FREE: case DRV_DISABLE: case DRV_ENABLE: case DRV_INSTALL: case DRV_REMOVE: case DRV_CONFIGURE: return 1; case DRV_QUERYCONFIGURE: case ICM_GETSTATE: case ICM_SETSTATE: return 0; case ICM_CONFIGURE: case ICM_ABOUT: return ICERR_UNSUPPORTED; // Open and close we need to handle - this is where we allocate // and free our private data structure. case DRV_OPEN: return (lParam2) ? HandleDriverOpen((ICOPEN FAR *)lParam2):1; case DRV_CLOSE: return HandleDriverClose(pDraw); // Code for drawing. case ICM_DRAW_BEGIN: return pDraw ? pDraw->Begin((ICDRAWBEGIN FAR *)lParam1) : ICERR_UNSUPPORTED; case ICM_DRAW: return pDraw ? pDraw->Draw((ICDRAW FAR *)lParam1) : ICERR_UNSUPPORTED; case ICM_DRAW_END: return pDraw ? pDraw->End() : ICERR_UNSUPPORTED ; case ICM_GETINFO: return ICERR_UNSUPPORTED; case ICM_DRAW_QUERY: return (pDraw && pDraw->CanHandleFormat((LPBITMAPINFOHEADER)lParam1)) ? ICERR_OK : ICERR_BADFORMAT; case ICM_DRAW_SUGGESTFORMAT: return pDraw ? pDraw->SuggestFormat((ICDRAWSUGGEST FAR *)lParam1) : ICERR_UNSUPPORTED; case ICM_DRAW_REALIZE: return pDraw ? pDraw->Realize((HDC)lParam1,(BOOL)lParam2) : ICERR_UNSUPPORTED; case ICM_DRAW_GET_PALETTE: return pDraw ? pDraw->GetPalette() : ICERR_UNSUPPORTED; case ICM_DRAW_CHANGEPALETTE: return pDraw ? pDraw->ChangePalette((LPBITMAPINFOHEADER)lParam1) : ICERR_UNSUPPORTED; } if (msg < DRV_USER) // Send all other standard installable driver messages for // default processing. return DefDriverProc(id,hDriver,msg,lParam1,lParam2); else // Anything else we don't support return ICERR_UNSUPPORTED; } static LRESULT HandleDriverOpen(ICOPEN FAR *pOpen) { LRESULT retVal = 0L; if(pOpen) { // We only accept video streams and we do not // handle compression and decompression. if (pOpen->fccType == streamtypeVIDEO && pOpen->dwFlags != ICMODE_COMPRESS && pOpen->dwFlags != ICMODE_DECOMPRESS) { // Allocate a private structure for storing information. DrawInfo *pDraw = new DrawInfo; if(pDraw) { pOpen->dwError = ICERR_OK; retVal = (LRESULT)(DrawInfo FAR *)pDraw; } else pOpen->dwError = ICERR_MEMORY; } } return retVal; } static LRESULT HandleDriverClose(DrawInfo *pDraw) { delete pDraw; // Destructor tidys up. return 1; } // Methods for class DrawInfo DrawInfo::DrawInfo(): pBuffer_(0), captionX_(0), captionY_(0), hWinGDC_(0), hWinGBitmap_(0) { hDD_ = DrawDibOpen(); hWinGDC_ = WinGCreateDC(); wsprintf(aCaption_,"Hello world"); } DrawInfo::~DrawInfo() { // Free any resources we still have. if(hDD_) DrawDibClose(hDD_); if(hWinGDC_ && hWinGBitmap_) DeleteObject(SelectObject(hWinGDC_,(HGDIOBJ)hOldBitmap_)); if(hWinGDC_) DeleteDC(hWinGDC_); } LRESULT DrawInfo::Begin(ICDRAWBEGIN FAR *pBegin) { struct { BITMAPINFOHEADER infoHeader; RGBQUAD colorTable[256]; } infoHeader; if(CanHandleFormat(pBegin->lpbi)) { // We may be called several times without a corresponding call to // several times so must delete the WinG bitmap if it already exists. if(hWinGBitmap_) { DeleteObject(SelectObject(hWinGDC_,(HGDIOBJ)hOldBitmap_)); hWinGBitmap_ =0; DrawDibEnd(hDD_); } hDC_ = pBegin->hdc; xDst_ = pBegin->xDst; yDst_ = pBegin->yDst; dxDst_ = pBegin->dxDst; dyDst_ = pBegin->dyDst; xSrc_ = pBegin->xSrc; ySrc_ = pBegin->ySrc; dxSrc_ = pBegin->dxSrc; dySrc_ = pBegin->dySrc; captionY_ = pBegin->dyDst/2; windowWidth_ = pBegin->dxDst; SetStretchBltMode(hDC_,COLORONCOLOR); if (DrawDibBegin(hDD_,hDC_,dxDst_,dyDst_,pBegin->lpbi,dxSrc_,dySrc_,0)) { hmemcpy(&infoHeader,pBegin->lpbi,sizeof(BITMAPINFOHEADER)); // Get the system palette entries. CopySystemPalette(infoHeader.colorTable); // Create the WinG bitmap. hWinGBitmap_ = WinGCreateBitmap(hWinGDC_,(LPBITMAPINFO)&infoHeader,&pBuffer_); if(hWinGBitmap_ && pBuffer_) { // Select the WinG bitmap into the WinG device context. hOldBitmap_ = (HBITMAP)SelectObject(hWinGDC_,(HGDIOBJ)hWinGBitmap_); return ICERR_OK; } else return ICERR_MEMORY; } else return ICERR_UNSUPPORTED; } else return ICERR_BADFORMAT; } LRESULT DrawInfo::Draw(ICDRAW FAR *pDrawStruct) { UINT wFlags; wFlags = DDF_SAME_HDC; if ((pDrawStruct->dwFlags & ICDRAW_NULLFRAME) || pDrawStruct->lpData == NULL) { if(pDrawStruct->dwFlags & ICDRAW_UPDATE) wFlags |= DDF_UPDATE; else return ICERR_OK; } if (pDrawStruct->dwFlags & ICDRAW_PREROLL) wFlags |= DDF_DONTDRAW; if (pDrawStruct->dwFlags & ICDRAW_HURRYUP) wFlags |= DDF_HURRYUP; // Compose the DIB in the WinG bitmap. ComposeFrame((LPBITMAPINFOHEADER)pDrawStruct->lpFormat,pDrawStruct->lpData); // Blt the WinG bitmap to the screen. if (!DrawDibDraw(hDD_,hDC_,xDst_,yDst_,dxDst_,dyDst_, (LPBITMAPINFOHEADER)pDrawStruct->lpFormat, pBuffer_,xSrc_, ySrc_,dxSrc_, dySrc_,wFlags)) { if (wFlags & DDF_UPDATE) return ICERR_CANTUPDATE; else return ICERR_UNSUPPORTED; } return ICERR_OK; } void DrawInfo::ComposeFrame(LPBITMAPINFOHEADER pInfoHeader, LPVOID pImageBits) { if(pBuffer_) { // Copy the bitmap we are given into the WinG bitmap. hmemcpy(pBuffer_,pImageBits,pInfoHeader->biSizeImage); SetBkMode(hWinGDC_,TRANSPARENT); SetTextColor(hWinGDC_,RGB(255,0,0)); // Draw 'Hello World' on top. TextOut(hWinGDC_,captionX_,captionY_,aCaption_,lstrlen(aCaption_)); // Update the position to draw the text - causes scrolling. captionX_ = (captionX_+1)%windowWidth_; } } LRESULT DrawInfo::End() { return ICERR_OK; } LRESULT DrawInfo::GetPalette() { return (LRESULT)(UINT)DrawDibGetPalette(hDD_); } LRESULT DrawInfo::ChangePalette(LPBITMAPINFOHEADER pInfoHeader) { PALETTEENTRY aPalette[256]; LPRGBQUAD pColors = (LPRGBQUAD)((LPBYTE)pInfoHeader + pInfoHeader->biSize); // That annoying RGB ordering problem again. for (int i=0; i<(int)pInfoHeader->biClrUsed; i++) { aPalette[i].peRed = pColors[i].rgbRed; aPalette[i].peGreen = pColors[i].rgbGreen; aPalette[i].peBlue = pColors[i].rgbBlue; aPalette[i].peFlags = 0; } DrawDibChangePalette(hDD_,0,(int)pInfoHeader->biClrUsed,aPalette); return ICERR_OK; } LRESULT DrawInfo::Realize(HDC hDC, BOOL background) { hDC_ = hDC; return (hDC_ && hDD_) ? DrawDibRealize(hDD_, hDC_,background) : ICERR_UNSUPPORTED; } BOOL DrawInfo::CanHandleFormat(LPBITMAPINFOHEADER pInfoHeader) { return (pInfoHeader && pInfoHeader->biCompression == BI_RGB && (pInfoHeader->biPlanes*pInfoHeader->biBitCount==8)) ? TRUE : FALSE; } LRESULT DrawInfo::SuggestFormat(ICDRAWSUGGEST FAR *pSuggest) { if (pSuggest->lpbiSuggest == NULL) return sizeof(BITMAPINFOHEADER) + 256 * sizeof(RGBQUAD); // We only want 8 bits-per-pixel uncompressed RGB DIBs. pSuggest->lpbiSuggest->biCompression = BI_RGB; pSuggest->lpbiSuggest->biPlanes = 1; pSuggest->lpbiSuggest->biBitCount = 8; return sizeof(BITMAPINFOHEADER) + pSuggest->lpbiSuggest->biClrUsed * sizeof(RGBQUAD); }