Chris is senior engineer at Symbionics Video Ltd., a technology development company that focuses on computer-video applications. He can be contacted at [email protected].
While Video for Windows (VfW) has been available for sometime, the only programming documentation for it is a help file supplied with the VfW developers kit. This lack of information is unfortunate since VfW is remarkably interesting and offers numerous opportunities for creative programmers.
For instance, writing a custom draw handler is a commonly used technique and is the basis of WinToon, the Microsoft cartoon engine. (WinToon is essentially a canned sprite playback engine for animators. Walt Disney's Lion King software, for example, is a WinToon app.) In this article, I'll develop a custom draw handler and use it in conjunction with Microsoft's games interface, WinG, to scroll text across a video window.
The Media Control Interface
With the release of Windows 3.1, Microsoft made multimedia support a core part of the operating environment. Windows 3.1 was initially geared toward wave-form audio and Musical Instrument Digital Interface (MIDI) devices. Consequently, a central part of the architecture is the Media Control Interface (MCI), which provides a uniform method of accessing multimedia devices.
From a programmer's perspective, MCI makes multimedia devices look like software VCRs, with commands such as play, pause, seek, and stop. The control of each MCI device is encapsulated in a driver called an "MCI command interpreter." This design permits the addition of new command interpreters as new devices become available.
The first version of MCI came with support for wave-form audio and MIDI devices. When VfW was released, it was shipped with its own command interpreter. A later version of VfW, built on top of MCI, provided the preregistered window class, MCIWnd, that supports video playback. MCIWnd makes writing VfW applications a straightforward process.
A Sample VfW Program
As Listing One shows, a video-playback program with considerable functionality can be written in less than 50 lines of code. Most of the code, in fact, has nothing to do with VfW and is merely the minimum code required to write a Windows program.
By using a modal dialog box, you avoid having to register a window class and create a message loop. An MCIWnd child window is created to fill the client area of the dialog box. This window displays the video image and provides a number of buttons for loading a video file and controlling its playback.
You create the MCIWnd window by calling the function MCIWndCreate() in response to the WM_ INITDIALOG message received by the dialog box. This function is flexible and accepts a number of flags that control window attributes, such as whether it has a menu or slider control. The MCIWNDF_NOTIFYSIZE flag requests the window to send notifications (using the MCIWNDM_NOTIFYSIZE message) to the dialog whenever the child window changes size. The dialog box responds to this message by resizing itself precisely to enclose the child MCIWnd window within its client area.
The final issue is handling palette changes brought about when the focus shifts between applications. In the dialog box, you must respond to WM_PALETTECHANGED and WM_QUERYNEWPALETTE messages and route them to the MCIWnd window for processing.
To build the program, you will need to get the VfW developer kit, which is available, among other sources, from the Microsoft Developers Network Level 2 CD-ROM. Note that the program I develop here requires the vfw.h header file and the vfw.lib library available from the MSDN. You also must include the mmsystem.lib library, a standard part of the Windows SDK that should come with your compiler.
This program is a fully functional playback application. It displays a small window with a play/stop button, menu button, and slider bar. The play/stop button is disabled until a video file is loaded. The menu button displays a pop-up menu, initially containing a single item for loading a video file. Once the file is loaded, the menu offers a variety of options, including controlling the video size and the audio volume. There are a couple of things you might like to try with these controls: 1. Hold down Ctrl while pressing the play button. This causes the video to play full screen. Be aware that not all display drivers support full-screen playback. 2.Hold the Shift key while pressing the play button. This plays the video backward. Although jerky, it does work.
The program also will play other multimedia files such as wave-form (.WAV) files.
What's Happening Under the Hood?
The example program is deceptively simple. Notice that the video appears to be playing in the background. This is because it is played under the control of a hidden program, MMTASK.TSK, started by the MCI subsystem. MMTASK.TSK is a program despite not having an .EXE extension.
Consider the sequence of actions MCI carries out to display the video in the way that we have seen:
- Read the compressed video and audio data from the file.
- Decompress the video.
- Decompress the audio.
- Send the decompressed video data to the display hardware.
- Send the decompressed audio data to the audio hardware.
At this point, the audio and video are still compressed and must be decompressed before rendering. VfW has two subsystems that handle this task. The installable compression manager (ICM) handles video, and the audio compression manager (ACM) handles audio. These two subsystems have a lot in common: Each uses a driver architecture in which the task of decompressing the data is delegated to DLLs known as "codecs." Once decompressed, the video and audio can be sent to the display and audio hardware. At this point, the video frame is a device independent bitmap (DIB) and is displayed using a high-performance bitblt function in the DrawDib subsystem. When VfW is installed or the display-driver mode is changed, DrawDib profiles the various methods of performing a bitblt and selects the quickest. The latest release of VfW (Version 1.1d) will use the display control interface (DCI) for accessing the frame buffer directly as long as a DCI provider is present.
That explanation gives a somewhat simplified view of what actually happens. Some video codecs, known as "rendering drivers," are capable of sending data directly to the display. They may even make use of video hardware for part of the decompression process, such as color-space conversion or scaling. The relationship between the ICM and DrawDib is quite tight. DrawDib will accept compressed video data and automatically send it to the ICM for decompression. The binding between the ACM and the Windows sound subsystem is equally tight. A component called the "wave-form mapper" intercepts any compressed audio data sent to the sound subsystem and routes it to the ACM for decompression.
A More Complicated VfW Program
The next example is similar to the last, except it scrolls the text "Hello World" across the video window. This effect is achieved by adding a custom draw handler to intercept the DIB just before it is displayed on the screen. It then draws the text into the DIB before rendering it using DrawDib. This illustrates a general technique that can be used for a variety of effects. For example, it is the method used by Michael Windser to implement WinToon. Like WinToon, this example uses WinG for drawing on the DIB.
Before going further, it is necessary to define a custom draw handler. To do that, we must take a further step back and explain what an installable driver is.
Installable Drivers and Draw Handlers
An installable driver is a DLL that has a particular entry point that must be called DriverProc(). The driver is registered with Windows using the Drivers applet in the control panel. If you start this applet, you can see the list of installable drivers present on your machine. Several components already mentioned--the audio and video codecs, the wave-form mapper, and the MCI command interpreters--are installable drivers. Windows uses the DriverProc to send messages to a driver in much the same way that it calls a window procedure to send messages to a window, although the set of messages is completely different.
The DriverProc has the following parameters:
- driverID is a driver-supplied value that the driver passes back to Windows when the driver is opened. This value is then passed to the driver on all subsequent calls to DriverProc.
- gDriver is a unique value assigned by Windows to identify the driver.
- msg identifies the message.
- lParam1 and lParam2 are 32-bit values, the meaning of which depends on the value of msg.
A draw handler is similar to the DriverProc of an installable driver in that it must have the same prototype, and it receives the same messages. A draw handler does not, however, need to be named DriverProc.
What is WinG?
Microsoft is anxious to make Windows a good platform for games. In support of this initiative, Microsoft has developed WinG, the games-programming interface, which was released late last year. A technique commonly used by games writers is to compose an image in an off-screen buffer before copying it to the display. This composition may involve using standard drawing primitives or, for some operations, direct manipulation of the bits of the image. Using both drawing primitives and direct manipulation is difficult in Windows because the bitmaps used by the graphic device interface (GDI) graphics engine are device dependent. WinG solves this problem by providing specialized device contexts and bitmaps. You can draw into a WinG bitmap using the standard GDI drawing primitives or directly manipulate it as a DIB. In the next example, I'll use both of these access methods.
In at the Deep End
The "Hello World" example program in Listing Two is based on the first example with the addition of a draw handler.
Since WinG can only cope with 256-color palletized displays, this program must be run in a 256-color display mode. You should call the function Is256ColorDisplay() in WinMain(), which performs the necessary check to ensure this is the case. Most of the remaining code you need to write deals with the draw handler together with the functions that it calls to process messages. A word of warning about the prolog code for the draw handler: Because it is called from within the context of MMTASK.TSK, smart callbacks will not work. You must call MakeProcInstance() to generate the code to correctly load the data segment on entry. This type of application is one of the few places where it is still necessary to use instance thunks in Windows programming.
You pass the MCIWNDF_NOTIFYMEDIA flag to MCIWndCreate(), requesting the MCIWnd window to send notifications (MCIWNDM_NOTIFYMEDIA) to the dialog whenever a new file is loaded. Install your draw handler in response to this message.
Many of the messages that your draw handler receives require little or no processing. In Listing Two, these messages are grouped together for convenience at the beginning of the draw handler.
The first message that your draw handler will receive is DRV_OPEN, and in response, you should allocate a data structure of type DrawInfo. This will be used to store information required for processing later messages. The address of this data structure should be returned from the draw handler and it will be passed as the driverId parameter of subsequent messages. In C++, structures can have methods as well as data members. For DrawInfo, this convenience allows you to write methods to handle each of the messages received by the draw handler. The constructor for the DrawInfo structure should allocate the WinG device context that will be used for drawing and also call DrawDibOpen() to register with DrawDib. The destructor for DrawDib must release any resources acquired by the draw handler and also deregister with DrawDib.
The next message you get is the ICM_ DRAW_SUGGESTFORMAT, asking which DIB formats you are prepared to accept. The proper response is, "8-bit-per-pixel uncompressed DIBs." VfW will attempt to find a codec that will convert the DIB to this format before passing it to you.
Before asking your draw handler to draw any DIBs, Windows will send the ICM_DRAW_BEGIN message, allowing you to perform any necessary preparation. There are several things you must then do. The DrawDib subsystem must be prepared by calling the function DrawDibBegin(), and you must call WinGCreateBitmap() to create a WinG bitmap, which will be used for drawing the text onto the DIB. This also is the point where you should store the source and destination rectangles in the DrawInfo structure. The ICM_DRAW_BEGIN message may be sent to you several times, and you should prevent resource leakage by deleting any WinG bitmap that you may have allocated in an earlier call.
Before you can draw any DIBs, the palette must be initialized correctly. You will receive the ICM_DRAW_REALIZE message asking you to realize the palette. You should call DrawDibRealize to do this for you.
And now to the main work of draw handler: You will be sent the ICM_DRAW message whenever you must render a DIB. For this, you should compose the DIB to be displayed in the WinG bitmap, then call DrawDibDraw() to display it. To compose the DIB, you should first copy the DIB you are given into the WinG bitmap, then use the GDI function TextOut() to write the text on top. In the example, the code to compose the DIB is confined into the method ComposeFrame(), making it easy for you to modify the composition and devise your own interesting effects.
Your draw handler must handle any palette change requests that may occur. Call DrawDibChangePalette to do this.
Finally, some advice on how to build the example application: You will need the VfW and WinG developer kits, which are both on the Microsoft Developers Network Level 2 and Multimedia Jumpstart 2.0 CD-ROMs. In addition, the WinG developer kit is available on the Internet from Microsoft's FTP site (ftp.microsoft.com) and in the Windows Multimedia forum on Compuserve (GO WINMM).
If the example works but displays garbage text rather than "Hello World," then you are probably using smart callbacks. See your compiler documentation for information on how to turn these off.
Where to Go from Here
For more information about VfW, you should refer to the help file that comes with it. At first sight, this can be somewhat intimidating because there is no architectural overview. However, the effort is worthwhile, since there is a wealth of information hidden within. It is also worth spending some time examining the sample applications that come with the VfW developer kit.
Listing One
#define STRICT #include <windows.h> #include <string.h> #include <vfw.h> 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 <windows.h> #include <windowsx.h> #include <string.h> #include <vfw.h> #include <mmsystem.h> #include <digitalv.h> #include <mciavi.h> #include <wing.h> // 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); }
Copyright © 1995, Dr. Dobb's Journal