//------------------------------------------------------------------------------ // File: WinUtil.cpp // // Desc: DirectShow base classes - implements generic window handler class. // // Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved. //------------------------------------------------------------------------------ #include #include #include #include #include static UINT MsgDestroy; // Constructor CBaseWindow::CBaseWindow(BOOL bDoGetDC, bool bDoPostToDestroy) : m_hInstance(g_hInst), m_hwnd(NULL), m_hdc(NULL), m_bActivated(FALSE), m_pClassName(NULL), m_ClassStyles(0), m_WindowStyles(0), m_WindowStylesEx(0), m_ShowStageMessage(0), m_ShowStageTop(0), m_MemoryDC(NULL), m_hPalette(NULL), m_bBackground(FALSE), #ifdef DEBUG m_bRealizing(FALSE), #endif m_bNoRealize(FALSE), m_bDoPostToDestroy(bDoPostToDestroy) { m_bDoGetDC = bDoGetDC; } // Prepare a window by spinning off a worker thread to do the creation and // also poll the message input queue. We leave this to be called by derived // classes because they might want to override methods like MessageLoop and // InitialiseWindow, if we do this during construction they'll ALWAYS call // this base class methods. We make the worker thread create the window so // it owns it rather than the filter graph thread which is constructing us HRESULT CBaseWindow::PrepareWindow() { if (m_hwnd) return NOERROR; ASSERT(m_hwnd == NULL); ASSERT(m_hdc == NULL); // Get the derived object's window and class styles m_pClassName = GetClassWindowStyles(&m_ClassStyles, &m_WindowStyles, &m_WindowStylesEx); if (m_pClassName == NULL) { return E_FAIL; } // Register our special private messages m_ShowStageMessage = RegisterWindowMessage(SHOWSTAGE); // RegisterWindowMessage() returns 0 if an error occurs. if (0 == m_ShowStageMessage) { return AmGetLastErrorToHResult(); } m_ShowStageTop = RegisterWindowMessage(SHOWSTAGETOP); if (0 == m_ShowStageTop) { return AmGetLastErrorToHResult(); } m_RealizePalette = RegisterWindowMessage(REALIZEPALETTE); if (0 == m_RealizePalette) { return AmGetLastErrorToHResult(); } MsgDestroy = RegisterWindowMessage(TEXT("AM_DESTROY")); if (0 == MsgDestroy) { return AmGetLastErrorToHResult(); } return DoCreateWindow(); } // Destructor just a placeholder so that we know it becomes virtual // Derived classes MUST call DoneWithWindow in their destructors so // that no messages arrive after the derived class constructor ends #ifdef DEBUG CBaseWindow::~CBaseWindow() { ASSERT(m_hwnd == NULL); ASSERT(m_hdc == NULL); } #endif // We use the sync worker event to have the window destroyed. All we do is // signal the event and wait on the window thread handle. Trying to send it // messages causes too many problems, furthermore to be on the safe side we // just wait on the thread handle while it returns WAIT_TIMEOUT or there is // a sent message to process on this thread. If the constructor failed to // create the thread in the first place then the loop will get terminated HRESULT CBaseWindow::DoneWithWindow() { if (!IsWindow(m_hwnd) || (GetWindowThreadProcessId(m_hwnd, NULL) != GetCurrentThreadId())) { if (IsWindow(m_hwnd)) { // This code should only be executed if the window exists and if the window's // messages are processed on a different thread. ASSERT(GetWindowThreadProcessId(m_hwnd, NULL) != GetCurrentThreadId()); if (m_bDoPostToDestroy) { HRESULT hr = S_OK; CAMEvent m_evDone(FALSE, &hr); if (FAILED(hr)) { return hr; } // We must post a message to destroy the window // That way we can't be in the middle of processing a // message posted to our window when we do go away // Sending a message gives less synchronization. PostMessage(m_hwnd, MsgDestroy, (WPARAM)(HANDLE)m_evDone, 0); WaitDispatchingMessages(m_evDone, INFINITE); } else { SendMessage(m_hwnd, MsgDestroy, 0, 0); } } // // This is not a leak, the window manager automatically free's // hdc's that were got via GetDC, which is the case here. // We set it to NULL so that we don't get any asserts later. // m_hdc = NULL; // // We need to free this DC though because USER32 does not know // anything about it. // if (m_MemoryDC) { EXECUTE_ASSERT(DeleteDC(m_MemoryDC)); m_MemoryDC = NULL; } // Reset the window variables m_hwnd = NULL; return NOERROR; } const HWND hwnd = m_hwnd; if (hwnd == NULL) { return NOERROR; } InactivateWindow(); NOTE("Inactivated"); // Reset the window styles before destruction SetWindowLong(hwnd,GWL_STYLE,m_WindowStyles); ASSERT(GetParent(hwnd) == NULL); NOTE1("Reset window styles %d",m_WindowStyles); // UnintialiseWindow sets m_hwnd to NULL so save a copy UninitialiseWindow(); DbgLog((LOG_TRACE, 2, TEXT("Destroying 0x%8.8X"), hwnd)); if (!DestroyWindow(hwnd)) { DbgLog((LOG_TRACE, 0, TEXT("DestroyWindow %8.8X failed code %d"), hwnd, GetLastError())); DbgBreak(""); } // Reset our state so we can be prepared again m_pClassName = NULL; m_ClassStyles = 0; m_WindowStyles = 0; m_WindowStylesEx = 0; m_ShowStageMessage = 0; m_ShowStageTop = 0; return NOERROR; } // Called at the end to put the window in an inactive state. The pending list // will always have been cleared by this time so event if the worker thread // gets has been signaled and gets in to render something it will find both // the state has been changed and that there are no available sample images // Since we wait on the window thread to complete we don't lock the object HRESULT CBaseWindow::InactivateWindow() { // Has the window been activated if (m_bActivated == FALSE) { return S_FALSE; } m_bActivated = FALSE; ShowWindow(m_hwnd,SW_HIDE); return NOERROR; } HRESULT CBaseWindow::CompleteConnect() { m_bActivated = FALSE; return NOERROR; } // This displays a normal window. We ask the base window class for default // sizes which unless overriden will return DEFWIDTH and DEFHEIGHT. We go // through a couple of extra hoops to get the client area the right size // as the object specifies which accounts for the AdjustWindowRectEx calls // We also DWORD align the left and top coordinates of the window here to // maximise the chance of being able to use DCI/DirectDraw primary surface HRESULT CBaseWindow::ActivateWindow() { // Has the window been sized and positioned already if (m_bActivated == TRUE || GetParent(m_hwnd) != NULL) { SetWindowPos(m_hwnd, // Our window handle HWND_TOP, // Put it at the top 0, 0, 0, 0, // Leave in current position SWP_NOMOVE | // Don't change it's place SWP_NOSIZE); // Change Z-order only m_bActivated = TRUE; return S_FALSE; } // Calculate the desired client rectangle RECT WindowRect, ClientRect = GetDefaultRect(); GetWindowRect(m_hwnd,&WindowRect); AdjustWindowRectEx(&ClientRect,GetWindowLong(m_hwnd,GWL_STYLE), FALSE,GetWindowLong(m_hwnd,GWL_EXSTYLE)); // Align left and top edges on DWORD boundaries UINT WindowFlags = (SWP_NOACTIVATE | SWP_FRAMECHANGED); WindowRect.left -= (WindowRect.left & 3); WindowRect.top -= (WindowRect.top & 3); SetWindowPos(m_hwnd, // Window handle HWND_TOP, // Put it at the top WindowRect.left, // Align left edge WindowRect.top, // And also top place WIDTH(&ClientRect), // Horizontal size HEIGHT(&ClientRect), // Vertical size WindowFlags); // Don't show window m_bActivated = TRUE; return NOERROR; } // This can be used to DWORD align the window for maximum performance HRESULT CBaseWindow::PerformanceAlignWindow() { RECT ClientRect,WindowRect; GetWindowRect(m_hwnd,&WindowRect); ASSERT(m_bActivated == TRUE); // Don't do this if we're owned if (GetParent(m_hwnd)) { return NOERROR; } // Align left and top edges on DWORD boundaries GetClientRect(m_hwnd, &ClientRect); MapWindowPoints(m_hwnd, HWND_DESKTOP, (LPPOINT) &ClientRect, 2); WindowRect.left -= (ClientRect.left & 3); WindowRect.top -= (ClientRect.top & 3); UINT WindowFlags = (SWP_NOACTIVATE | SWP_NOSIZE); SetWindowPos(m_hwnd, // Window handle HWND_TOP, // Put it at the top WindowRect.left, // Align left edge WindowRect.top, // And also top place (int) 0,(int) 0, // Ignore these sizes WindowFlags); // Don't show window return NOERROR; } // Install a palette into the base window - we may be called by a different // thread to the one that owns the window. We have to be careful how we do // the palette realisation as we could be a different thread to the window // which would cause an inter thread send message. Therefore we realise the // palette by sending it a special message but without the window locked HRESULT CBaseWindow::SetPalette(HPALETTE hPalette) { // We must own the window lock during the change { CAutoLock cWindowLock(&m_WindowLock); CAutoLock cPaletteLock(&m_PaletteLock); ASSERT(hPalette); m_hPalette = hPalette; } return SetPalette(); } HRESULT CBaseWindow::SetPalette() { if (!m_bNoRealize) { SendMessage(m_hwnd, m_RealizePalette, 0, 0); return S_OK; } else { // Just select the palette ASSERT(m_hdc); ASSERT(m_MemoryDC); CAutoLock cPaletteLock(&m_PaletteLock); SelectPalette(m_hdc,m_hPalette,m_bBackground); SelectPalette(m_MemoryDC,m_hPalette,m_bBackground); return S_OK; } } void CBaseWindow::UnsetPalette() { CAutoLock cWindowLock(&m_WindowLock); CAutoLock cPaletteLock(&m_PaletteLock); // Get a standard VGA colour palette HPALETTE hPalette = (HPALETTE) GetStockObject(DEFAULT_PALETTE); ASSERT(hPalette); SelectPalette(GetWindowHDC(), hPalette, TRUE); SelectPalette(GetMemoryHDC(), hPalette, TRUE); m_hPalette = NULL; } void CBaseWindow::LockPaletteLock() { m_PaletteLock.Lock(); } void CBaseWindow::UnlockPaletteLock() { m_PaletteLock.Unlock(); } // Realise our palettes in the window and device contexts HRESULT CBaseWindow::DoRealisePalette(BOOL bForceBackground) { { CAutoLock cPaletteLock(&m_PaletteLock); if (m_hPalette == NULL) { return NOERROR; } // Realize the palette on the window thread ASSERT(m_hdc); ASSERT(m_MemoryDC); SelectPalette(m_hdc,m_hPalette,m_bBackground || bForceBackground); SelectPalette(m_MemoryDC,m_hPalette,m_bBackground); } // If we grab a critical section here we can deadlock // with the window thread because one of the side effects // of RealizePalette is to send a WM_PALETTECHANGED message // to every window in the system. In our handling // of WM_PALETTECHANGED we used to grab this CS too. // The really bad case is when our renderer calls DoRealisePalette() // while we're in the middle of processing a palette change // for another window. // So don't hold the critical section while actually realising // the palette. In any case USER is meant to manage palette // handling - we shouldn't have to serialize everything as well ASSERT(CritCheckOut(&m_WindowLock)); ASSERT(CritCheckOut(&m_PaletteLock)); EXECUTE_ASSERT(RealizePalette(m_hdc) != GDI_ERROR); EXECUTE_ASSERT(RealizePalette(m_MemoryDC) != GDI_ERROR); return (GdiFlush() == FALSE ? S_FALSE : S_OK); } // This is the global window procedure LRESULT CALLBACK WndProc(HWND hwnd, // Window handle UINT uMsg, // Message ID WPARAM wParam, // First parameter LPARAM lParam) // Other parameter { // Get the window long that holds our window object pointer // If it is NULL then we are initialising the window in which // case the object pointer has been passed in the window creation // structure. IF we get any messages before WM_NCCREATE we will // pass them to DefWindowProc. CBaseWindow *pBaseWindow = _GetWindowLongPtr(hwnd,0); if (pBaseWindow == NULL) { // Get the structure pointer from the create struct. // We can only do this for WM_NCCREATE which should be one of // the first messages we receive. Anything before this will // have to be passed to DefWindowProc (i.e. WM_GETMINMAXINFO) // If the message is WM_NCCREATE we set our pBaseWindow pointer // and will then place it in the window structure // turn off WS_EX_LAYOUTRTL style for quartz windows if (uMsg == WM_NCCREATE) { SetWindowLong(hwnd, GWL_EXSTYLE, GetWindowLong(hwnd, GWL_EXSTYLE) & ~0x400000); } if ((uMsg != WM_NCCREATE) || (NULL == (pBaseWindow = *(CBaseWindow**) ((LPCREATESTRUCT)lParam)->lpCreateParams))) { return(DefWindowProc(hwnd, uMsg, wParam, lParam)); } // Set the window LONG to be the object who created us #ifdef DEBUG SetLastError(0); // because of the way SetWindowLong works #endif LONG_PTR rc = _SetWindowLongPtr(hwnd, (DWORD) 0, pBaseWindow); #ifdef DEBUG if (0 == rc) { // SetWindowLong MIGHT have failed. (Read the docs which admit // that it is awkward to work out if you have had an error.) LONG lasterror = GetLastError(); ASSERT(0 == lasterror); // If this is not the case we have not set the pBaseWindow pointer // into the window structure and we will blow up. } #endif } // See if this is the packet of death if (uMsg == MsgDestroy && uMsg != 0) { pBaseWindow->DoneWithWindow(); if (pBaseWindow->m_bDoPostToDestroy) { EXECUTE_ASSERT(SetEvent((HANDLE)wParam)); } return 0; } return pBaseWindow->OnReceiveMessage(hwnd,uMsg,wParam,lParam); } // When the window size changes we adjust our member variables that // contain the dimensions of the client rectangle for our window so // that we come to render an image we will know whether to stretch BOOL CBaseWindow::OnSize(LONG Width, LONG Height) { m_Width = Width; m_Height = Height; return TRUE; } // This function handles the WM_CLOSE message BOOL CBaseWindow::OnClose() { ShowWindow(m_hwnd,SW_HIDE); return TRUE; } // This is called by the worker window thread when it receives a terminate // message from the window object destructor to delete all the resources we // allocated during initialisation. By the time the worker thread exits all // processing will have been completed as the source filter disconnection // flushes the image pending sample, therefore the GdiFlush should succeed HRESULT CBaseWindow::UninitialiseWindow() { // Have we already cleaned up if (m_hwnd == NULL) { ASSERT(m_hdc == NULL); ASSERT(m_MemoryDC == NULL); return NOERROR; } // Release the window resources EXECUTE_ASSERT(GdiFlush()); if (m_hdc) { EXECUTE_ASSERT(ReleaseDC(m_hwnd,m_hdc)); m_hdc = NULL; } if (m_MemoryDC) { EXECUTE_ASSERT(DeleteDC(m_MemoryDC)); m_MemoryDC = NULL; } // Reset the window variables m_hwnd = NULL; return NOERROR; } // This is called by the worker window thread after it has created the main // window and it wants to initialise the rest of the owner objects window // variables such as the device contexts. We execute this function with the // critical section still locked. Nothing in this function must generate any // SendMessage calls to the window because this is executing on the window // thread so the message will never be processed and we will deadlock HRESULT CBaseWindow::InitialiseWindow(HWND hwnd) { // Initialise the window variables ASSERT(IsWindow(hwnd)); m_hwnd = hwnd; if (m_bDoGetDC) { EXECUTE_ASSERT(m_hdc = GetDC(hwnd)); EXECUTE_ASSERT(m_MemoryDC = CreateCompatibleDC(m_hdc)); EXECUTE_ASSERT(SetStretchBltMode(m_hdc,COLORONCOLOR)); EXECUTE_ASSERT(SetStretchBltMode(m_MemoryDC,COLORONCOLOR)); } return NOERROR; } HRESULT CBaseWindow::DoCreateWindow() { WNDCLASS wndclass; // Used to register classes BOOL bRegistered; // Is this class registered HWND hwnd; // Handle to our window bRegistered = GetClassInfo(m_hInstance, // Module instance m_pClassName, // Window class &wndclass); // Info structure // if the window is to be used for drawing puposes and we are getting a DC // for the entire lifetime of the window then changes the class style to do // say so. If we don't set this flag then the DC comes from the cache and is // really bad. if (m_bDoGetDC) { m_ClassStyles |= CS_OWNDC; } if (bRegistered == FALSE) { // Register the renderer window class wndclass.lpszClassName = m_pClassName; wndclass.style = m_ClassStyles; wndclass.lpfnWndProc = WndProc; wndclass.cbClsExtra = 0; wndclass.cbWndExtra = sizeof(CBaseWindow *); wndclass.hInstance = m_hInstance; wndclass.hIcon = NULL; wndclass.hCursor = LoadCursor (NULL, IDC_ARROW); wndclass.hbrBackground = (HBRUSH) NULL; wndclass.lpszMenuName = NULL; RegisterClass(&wndclass); } // Create the frame window. Pass the pBaseWindow information in the // CreateStruct which allows our message handling loop to get hold of // the pBaseWindow pointer. CBaseWindow *pBaseWindow = this; // The owner window object hwnd = CreateWindowEx(m_WindowStylesEx, // Extended styles m_pClassName, // Registered name TEXT("ActiveMovie Window"), // Window title m_WindowStyles, // Window styles CW_USEDEFAULT, // Start x position CW_USEDEFAULT, // Start y position DEFWIDTH, // Window width DEFHEIGHT, // Window height NULL, // Parent handle NULL, // Menu handle m_hInstance, // Instance handle &pBaseWindow); // Creation data // If we failed signal an error to the object constructor (based on the // last Win32 error on this thread) then signal the constructor thread // to continue, release the mutex to let others have a go and exit if (hwnd == NULL) { DWORD Error = GetLastError(); return AmHresultFromWin32(Error); } // Check the window LONG is the object who created us ASSERT(GetWindowLongPtr(hwnd, 0) == (LONG_PTR)this); // Initialise the window and then signal the constructor so that it can // continue and then finally unlock the object's critical section. The // window class is left registered even after we terminate the thread // as we don't know when the last window has been closed. So we allow // the operating system to free the class resources as appropriate InitialiseWindow(hwnd); DbgLog((LOG_TRACE, 2, TEXT("Created window class (%s) HWND(%8.8X)"), m_pClassName, hwnd)); return S_OK; } // The base class provides some default handling and calls DefWindowProc LRESULT CBaseWindow::OnReceiveMessage(HWND hwnd, // Window handle UINT uMsg, // Message ID WPARAM wParam, // First parameter LPARAM lParam) // Other parameter { ASSERT(IsWindow(hwnd)); if (PossiblyEatMessage(uMsg, wParam, lParam)) return 0; // This is sent by the IVideoWindow SetWindowForeground method. If the // window is invisible we will show it and make it topmost without the // foreground focus. If the window is visible it will also be made the // topmost window without the foreground focus. If wParam is TRUE then // for both cases the window will be forced into the foreground focus if (uMsg == m_ShowStageMessage) { BOOL bVisible = IsWindowVisible(hwnd); SetWindowPos(hwnd, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW | (bVisible ? SWP_NOACTIVATE : 0)); // Should we bring the window to the foreground if (wParam == TRUE) { SetForegroundWindow(hwnd); } return (LRESULT) 1; } // When we go fullscreen we have to add the WS_EX_TOPMOST style to the // video window so that it comes out above any task bar (this is more // relevant to WindowsNT than Windows95). However the SetWindowPos call // must be on the same thread as that which created the window. The // wParam parameter can be TRUE or FALSE to set and reset the topmost if (uMsg == m_ShowStageTop) { HWND HwndTop = (wParam == TRUE ? HWND_TOPMOST : HWND_NOTOPMOST); BOOL bVisible = IsWindowVisible(hwnd); SetWindowPos(hwnd, HwndTop, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | (wParam == TRUE ? SWP_SHOWWINDOW : 0) | (bVisible ? SWP_NOACTIVATE : 0)); return (LRESULT) 1; } // New palette stuff if (uMsg == m_RealizePalette) { ASSERT(m_hwnd == hwnd); return OnPaletteChange(m_hwnd,WM_QUERYNEWPALETTE); } switch (uMsg) { // Repaint the window if the system colours change case WM_SYSCOLORCHANGE: InvalidateRect(hwnd,NULL,FALSE); return (LRESULT) 1; // Somebody has changed the palette case WM_PALETTECHANGED: OnPaletteChange((HWND)wParam,uMsg); return (LRESULT) 0; // We are about to receive the keyboard focus so we ask GDI to realise // our logical palette again and hopefully it will be fully installed // without any mapping having to be done during any picture rendering case WM_QUERYNEWPALETTE: ASSERT(m_hwnd == hwnd); return OnPaletteChange(m_hwnd,uMsg); // do NOT fwd WM_MOVE. the parameters are the location of the parent // window, NOT what the renderer should be looking at. But we need // to make sure the overlay is moved with the parent window, so we // do this. case WM_MOVE: if (IsWindowVisible(m_hwnd)) { PostMessage(m_hwnd,WM_PAINT,0,0); } break; // Store the width and height as useful base class members case WM_SIZE: OnSize(LOWORD(lParam), HIWORD(lParam)); return (LRESULT) 0; // Intercept the WM_CLOSE messages to hide the window case WM_CLOSE: OnClose(); return (LRESULT) 0; } return DefWindowProc(hwnd,uMsg,wParam,lParam); } // This handles the Windows palette change messages - if we do realise our // palette then we return TRUE otherwise we return FALSE. If our window is // foreground application then we should get first choice of colours in the // system palette entries. We get best performance when our logical palette // includes the standard VGA colours (at the beginning and end) otherwise // GDI may have to map from our palette to the device palette while drawing LRESULT CBaseWindow::OnPaletteChange(HWND hwnd,UINT Message) { // First check we are not changing the palette during closedown if (m_hwnd == NULL || hwnd == NULL) { return (LRESULT) 0; } ASSERT(!m_bRealizing); // Should we realise our palette again if ((Message == WM_QUERYNEWPALETTE || hwnd != m_hwnd)) { // It seems that even if we're invisible that we can get asked // to realize our palette and this can cause really ugly side-effects // Seems like there's another bug but this masks it a least for the // shutting down case. if (!IsWindowVisible(m_hwnd)) { DbgLog((LOG_TRACE, 1, TEXT("Realizing when invisible!"))); return (LRESULT) 0; } // Avoid recursion with multiple graphs in the same app #ifdef DEBUG m_bRealizing = TRUE; #endif DoRealisePalette(Message != WM_QUERYNEWPALETTE); #ifdef DEBUG m_bRealizing = FALSE; #endif // Should we redraw the window with the new palette if (Message == WM_PALETTECHANGED) { InvalidateRect(m_hwnd,NULL,FALSE); } } return (LRESULT) 1; } // Determine if the window exists. bool CBaseWindow::WindowExists() { return !!IsWindow(m_hwnd); } // Return the default window rectangle RECT CBaseWindow::GetDefaultRect() { RECT DefaultRect = {0,0,DEFWIDTH,DEFHEIGHT}; ASSERT(m_hwnd); // ASSERT(m_hdc); return DefaultRect; } // Return the current window width LONG CBaseWindow::GetWindowWidth() { ASSERT(m_hwnd); // ASSERT(m_hdc); return m_Width; } // Return the current window height LONG CBaseWindow::GetWindowHeight() { ASSERT(m_hwnd); // ASSERT(m_hdc); return m_Height; } // Return the window handle HWND CBaseWindow::GetWindowHWND() { ASSERT(m_hwnd); // ASSERT(m_hdc); return m_hwnd; } // Return the window drawing device context HDC CBaseWindow::GetWindowHDC() { ASSERT(m_hwnd); ASSERT(m_hdc); return m_hdc; } // Return the offscreen window drawing device context HDC CBaseWindow::GetMemoryHDC() { ASSERT(m_hwnd); ASSERT(m_MemoryDC); return m_MemoryDC; } #ifdef DEBUG HPALETTE CBaseWindow::GetPalette() { // The palette lock should always be held when accessing // m_hPalette. ASSERT(CritCheckIn(&m_PaletteLock)); return m_hPalette; } #endif // DEBUG // This is available to clients who want to change the window visiblity. It's // little more than an indirection to the Win32 ShowWindow although these is // some benefit in going through here as this function may change sometime HRESULT CBaseWindow::DoShowWindow(LONG ShowCmd) { ShowWindow(m_hwnd,ShowCmd); return NOERROR; } // Generate a WM_PAINT message for the video window void CBaseWindow::PaintWindow(BOOL bErase) { InvalidateRect(m_hwnd,NULL,bErase); } // Allow an application to have us set the video window in the foreground. We // have this because it is difficult for one thread to do do this to a window // owned by another thread. Rather than expose the message we use to execute // the inter thread send message we provide the interface function. All we do // is to SendMessage to the video window renderer thread with a WM_SHOWSTAGE void CBaseWindow::DoSetWindowForeground(BOOL bFocus) { SendMessage(m_hwnd,m_ShowStageMessage,(WPARAM) bFocus,(LPARAM) 0); } // Constructor initialises the owning object pointer. Since we are a worker // class for the main window object we have relatively few state variables to // look after. We are given device context handles to use later on as well as // the source and destination rectangles (but reset them here just in case) CDrawImage::CDrawImage(__inout CBaseWindow *pBaseWindow) : m_pBaseWindow(pBaseWindow), m_hdc(NULL), m_MemoryDC(NULL), m_bStretch(FALSE), m_pMediaType(NULL), m_bUsingImageAllocator(FALSE) { ASSERT(pBaseWindow); ResetPaletteVersion(); SetRectEmpty(&m_TargetRect); SetRectEmpty(&m_SourceRect); m_perfidRenderTime = MSR_REGISTER(TEXT("Single Blt time")); } // Overlay the image time stamps on the picture. Access to this method is // serialised by the caller. We display the sample start and end times on // top of the video using TextOut on the device context we are handed. If // there isn't enough room in the window for the times we don't show them void CDrawImage::DisplaySampleTimes(IMediaSample *pSample) { #ifdef DEBUG // // Only allow the "annoying" time messages if the users has turned the // logging "way up" // BOOL bAccept = DbgCheckModuleLevel(LOG_TRACE, 5); if (bAccept == FALSE) { return; } #endif TCHAR szTimes[TIMELENGTH]; // Time stamp strings ASSERT(pSample); // Quick sanity check RECT ClientRect; // Client window size SIZE Size; // Size of text output // Get the time stamps and window size pSample->GetTime((REFERENCE_TIME*)&m_StartSample, (REFERENCE_TIME*)&m_EndSample); HWND hwnd = m_pBaseWindow->GetWindowHWND(); EXECUTE_ASSERT(GetClientRect(hwnd,&ClientRect)); // Format the sample time stamps (void)StringCchPrintf(szTimes,NUMELMS(szTimes),TEXT("%08d : %08d"), m_StartSample.Millisecs(), m_EndSample.Millisecs()); ASSERT(lstrlen(szTimes) < TIMELENGTH); // Put the times in the middle at the bottom of the window GetTextExtentPoint32(m_hdc,szTimes,lstrlen(szTimes),&Size); INT XPos = ((ClientRect.right - ClientRect.left) - Size.cx) / 2; INT YPos = ((ClientRect.bottom - ClientRect.top) - Size.cy) * 4 / 5; // Check the window is big enough to have sample times displayed if ((XPos > 0) && (YPos > 0)) { TextOut(m_hdc,XPos,YPos,szTimes,lstrlen(szTimes)); } } // This is called when the drawing code sees that the image has a down level // palette cookie. We simply call the SetDIBColorTable Windows API with the // palette that is found after the BITMAPINFOHEADER - we return no errors void CDrawImage::UpdateColourTable(HDC hdc,__in BITMAPINFOHEADER *pbmi) { ASSERT(pbmi->biClrUsed); RGBQUAD *pColourTable = (RGBQUAD *)(pbmi+1); // Set the new palette in the device context UINT uiReturn = SetDIBColorTable(hdc,(UINT) 0, pbmi->biClrUsed, pColourTable); // Should always succeed but check in debug builds ASSERT(uiReturn == pbmi->biClrUsed); } // No source rectangle scaling is done by the base class RECT CDrawImage::ScaleSourceRect(const RECT *pSource) { ASSERT(pSource); return *pSource; } // This is called when the funky output pin uses our allocator. The samples we // allocate are special because the memory is shared between us and GDI thus // removing one copy when we ask for the image to be rendered. The source type // information is in the main renderer m_mtIn field which is initialised when // the media type is agreed in SetMediaType, the media type may be changed on // the fly if, for example, the source filter needs to change the palette void CDrawImage::FastRender(IMediaSample *pMediaSample) { BITMAPINFOHEADER *pbmi; // Image format data DIBDATA *pDibData; // Stores DIB information BYTE *pImage; // Pointer to image data HBITMAP hOldBitmap; // Store the old bitmap CImageSample *pSample; // Pointer to C++ object ASSERT(m_pMediaType); // From the untyped source format block get the VIDEOINFO and subsequently // the BITMAPINFOHEADER structure. We can cast the IMediaSample interface // to a CImageSample object so we can retrieve it's DIBSECTION details pbmi = HEADER(m_pMediaType->Format()); pSample = (CImageSample *) pMediaSample; pDibData = pSample->GetDIBData(); hOldBitmap = (HBITMAP) SelectObject(m_MemoryDC,pDibData->hBitmap); // Get a pointer to the real image data HRESULT hr = pMediaSample->GetPointer(&pImage); if (FAILED(hr)) { return; } // Do we need to update the colour table, we increment our palette cookie // each time we get a dynamic format change. The sample palette cookie is // stored in the DIBDATA structure so we try to keep the fields in sync // By the time we get to draw the images the format change will be done // so all we do is ask the renderer for what it's palette version is if (pDibData->PaletteVersion < GetPaletteVersion()) { ASSERT(pbmi->biBitCount <= iPALETTE); UpdateColourTable(m_MemoryDC,pbmi); pDibData->PaletteVersion = GetPaletteVersion(); } // This allows derived classes to change the source rectangle that we do // the drawing with. For example a renderer may ask a codec to stretch // the video from 320x240 to 640x480, in which case the source we see in // here will still be 320x240, although the source we want to draw with // should be scaled up to 640x480. The base class implementation of this // method does nothing but return the same rectangle as we are passed in RECT SourceRect = ScaleSourceRect(&m_SourceRect); // Is the window the same size as the video if (m_bStretch == FALSE) { // Put the image straight into the window BitBlt( (HDC) m_hdc, // Target device HDC m_TargetRect.left, // X sink position m_TargetRect.top, // Y sink position m_TargetRect.right - m_TargetRect.left, // Destination width m_TargetRect.bottom - m_TargetRect.top, // Destination height m_MemoryDC, // Source device context SourceRect.left, // X source position SourceRect.top, // Y source position SRCCOPY); // Simple copy } else { // Stretch the image when copying to the window StretchBlt( (HDC) m_hdc, // Target device HDC m_TargetRect.left, // X sink position m_TargetRect.top, // Y sink position m_TargetRect.right - m_TargetRect.left, // Destination width m_TargetRect.bottom - m_TargetRect.top, // Destination height m_MemoryDC, // Source device HDC SourceRect.left, // X source position SourceRect.top, // Y source position SourceRect.right - SourceRect.left, // Source width SourceRect.bottom - SourceRect.top, // Source height SRCCOPY); // Simple copy } // This displays the sample times over the top of the image. This used to // draw the times into the offscreen device context however that actually // writes the text into the image data buffer which may not be writable #ifdef DEBUG DisplaySampleTimes(pMediaSample); #endif // Put the old bitmap back into the device context so we don't leak SelectObject(m_MemoryDC,hOldBitmap); } // This is called when there is a sample ready to be drawn, unfortunately the // output pin was being rotten and didn't choose our super excellent shared // memory DIB allocator so we have to do this slow render using boring old GDI // SetDIBitsToDevice and StretchDIBits. The down side of using these GDI // functions is that the image data has to be copied across from our address // space into theirs before going to the screen (although in reality the cost // is small because all they do is to map the buffer into their address space) void CDrawImage::SlowRender(IMediaSample *pMediaSample) { // Get the BITMAPINFOHEADER for the connection ASSERT(m_pMediaType); BITMAPINFOHEADER *pbmi = HEADER(m_pMediaType->Format()); BYTE *pImage; // Get the image data buffer HRESULT hr = pMediaSample->GetPointer(&pImage); if (FAILED(hr)) { return; } // This allows derived classes to change the source rectangle that we do // the drawing with. For example a renderer may ask a codec to stretch // the video from 320x240 to 640x480, in which case the source we see in // here will still be 320x240, although the source we want to draw with // should be scaled up to 640x480. The base class implementation of this // method does nothing but return the same rectangle as we are passed in RECT SourceRect = ScaleSourceRect(&m_SourceRect); LONG lAdjustedSourceTop = SourceRect.top; // if the origin of bitmap is bottom-left, adjust soruce_rect_top // to be the bottom-left corner instead of the top-left. if (pbmi->biHeight > 0) { lAdjustedSourceTop = pbmi->biHeight - SourceRect.bottom; } // Is the window the same size as the video if (m_bStretch == FALSE) { // Put the image straight into the window SetDIBitsToDevice( (HDC) m_hdc, // Target device HDC m_TargetRect.left, // X sink position m_TargetRect.top, // Y sink position m_TargetRect.right - m_TargetRect.left, // Destination width m_TargetRect.bottom - m_TargetRect.top, // Destination height SourceRect.left, // X source position lAdjustedSourceTop, // Adjusted Y source position (UINT) 0, // Start scan line pbmi->biHeight, // Scan lines present pImage, // Image data (BITMAPINFO *) pbmi, // DIB header DIB_RGB_COLORS); // Type of palette } else { // Stretch the image when copying to the window StretchDIBits( (HDC) m_hdc, // Target device HDC m_TargetRect.left, // X sink position m_TargetRect.top, // Y sink position m_TargetRect.right - m_TargetRect.left, // Destination width m_TargetRect.bottom - m_TargetRect.top, // Destination height SourceRect.left, // X source position lAdjustedSourceTop, // Adjusted Y source position SourceRect.right - SourceRect.left, // Source width SourceRect.bottom - SourceRect.top, // Source height pImage, // Image data (BITMAPINFO *) pbmi, // DIB header DIB_RGB_COLORS, // Type of palette SRCCOPY); // Simple image copy } // This shows the sample reference times over the top of the image which // looks a little flickery. I tried using GdiSetBatchLimit and GdiFlush to // control the screen updates but it doesn't quite work as expected and // only partially reduces the flicker. I also tried using a memory context // and combining the two in that before doing a final BitBlt operation to // the screen, unfortunately this has considerable performance penalties // and also means that this code is not executed when compiled retail #ifdef DEBUG DisplaySampleTimes(pMediaSample); #endif } // This is called with an IMediaSample interface on the image to be drawn. We // decide on the drawing mechanism based on who's allocator we are using. We // may be called when the window wants an image painted by WM_PAINT messages // We can't realise the palette here because we have the renderer lock, any // call to realise may cause an interthread send message to the window thread // which may in turn be waiting to get the renderer lock before servicing it BOOL CDrawImage::DrawImage(IMediaSample *pMediaSample) { ASSERT(m_hdc); ASSERT(m_MemoryDC); NotifyStartDraw(); // If the output pin used our allocator then the samples passed are in // fact CVideoSample objects that contain CreateDIBSection data that we // use to do faster image rendering, they may optionally also contain a // DirectDraw surface pointer in which case we do not do the drawing if (m_bUsingImageAllocator == FALSE) { SlowRender(pMediaSample); EXECUTE_ASSERT(GdiFlush()); NotifyEndDraw(); return TRUE; } // This is a DIBSECTION buffer FastRender(pMediaSample); EXECUTE_ASSERT(GdiFlush()); NotifyEndDraw(); return TRUE; } BOOL CDrawImage::DrawVideoImageHere( HDC hdc, IMediaSample *pMediaSample, __in LPRECT lprcSrc, __in LPRECT lprcDst ) { ASSERT(m_pMediaType); BITMAPINFOHEADER *pbmi = HEADER(m_pMediaType->Format()); BYTE *pImage; // Get the image data buffer HRESULT hr = pMediaSample->GetPointer(&pImage); if (FAILED(hr)) { return FALSE; } RECT SourceRect; RECT TargetRect; if (lprcSrc) { SourceRect = *lprcSrc; } else SourceRect = ScaleSourceRect(&m_SourceRect); if (lprcDst) { TargetRect = *lprcDst; } else TargetRect = m_TargetRect; LONG lAdjustedSourceTop = SourceRect.top; // if the origin of bitmap is bottom-left, adjust soruce_rect_top // to be the bottom-left corner instead of the top-left. if (pbmi->biHeight > 0) { lAdjustedSourceTop = pbmi->biHeight - SourceRect.bottom; } // Stretch the image when copying to the DC BOOL bRet = (0 != StretchDIBits(hdc, TargetRect.left, TargetRect.top, TargetRect.right - TargetRect.left, TargetRect.bottom - TargetRect.top, SourceRect.left, lAdjustedSourceTop, SourceRect.right - SourceRect.left, SourceRect.bottom - SourceRect.top, pImage, (BITMAPINFO *)pbmi, DIB_RGB_COLORS, SRCCOPY)); return bRet; } // This is called by the owning window object after it has created the window // and it's drawing contexts. We are constructed with the base window we'll // be drawing into so when given the notification we retrive the device HDCs // to draw with. We cannot call these in our constructor as they are virtual void CDrawImage::SetDrawContext() { m_MemoryDC = m_pBaseWindow->GetMemoryHDC(); m_hdc = m_pBaseWindow->GetWindowHDC(); } // This is called to set the target rectangle in the video window, it will be // called whenever a WM_SIZE message is retrieved from the message queue. We // simply store the rectangle and use it later when we do the drawing calls void CDrawImage::SetTargetRect(__in RECT *pTargetRect) { ASSERT(pTargetRect); m_TargetRect = *pTargetRect; SetStretchMode(); } // Return the current target rectangle void CDrawImage::GetTargetRect(__out RECT *pTargetRect) { ASSERT(pTargetRect); *pTargetRect = m_TargetRect; } // This is called when we want to change the section of the image to draw. We // use this information in the drawing operation calls later on. We must also // see if the source and destination rectangles have the same dimensions. If // not we must stretch during the drawing rather than a direct pixel copy void CDrawImage::SetSourceRect(__in RECT *pSourceRect) { ASSERT(pSourceRect); m_SourceRect = *pSourceRect; SetStretchMode(); } // Return the current source rectangle void CDrawImage::GetSourceRect(__out RECT *pSourceRect) { ASSERT(pSourceRect); *pSourceRect = m_SourceRect; } // This is called when either the source or destination rectanges change so we // can update the stretch flag. If the rectangles don't match we stretch the // video during the drawing otherwise we call the fast pixel copy functions // NOTE the source and/or the destination rectangle may be completely empty void CDrawImage::SetStretchMode() { // Calculate the overall rectangle dimensions LONG SourceWidth = m_SourceRect.right - m_SourceRect.left; LONG SinkWidth = m_TargetRect.right - m_TargetRect.left; LONG SourceHeight = m_SourceRect.bottom - m_SourceRect.top; LONG SinkHeight = m_TargetRect.bottom - m_TargetRect.top; m_bStretch = TRUE; if (SourceWidth == SinkWidth) { if (SourceHeight == SinkHeight) { m_bStretch = FALSE; } } } // Tell us whose allocator we are using. This should be called with TRUE if // the filter agrees to use an allocator based around the CImageAllocator // SDK base class - whose image buffers are made through CreateDIBSection. // Otherwise this should be called with FALSE and we will draw the images // using SetDIBitsToDevice and StretchDIBitsToDevice. None of these calls // can handle buffers which have non zero strides (like DirectDraw uses) void CDrawImage::NotifyAllocator(BOOL bUsingImageAllocator) { m_bUsingImageAllocator = bUsingImageAllocator; } // Are we using the image DIBSECTION allocator BOOL CDrawImage::UsingImageAllocator() { return m_bUsingImageAllocator; } // We need the media type of the connection so that we can get the BITMAPINFO // from it. We use that in the calls to draw the image such as StretchDIBits // and also when updating the colour table held in shared memory DIBSECTIONs void CDrawImage::NotifyMediaType(__in CMediaType *pMediaType) { m_pMediaType = pMediaType; } // We store in this object a cookie maintaining the current palette version. // Each time a palettised format is changed we increment this value so that // when we come to draw the images we look at the colour table value they // have and if less than the current we know to update it. This version is // only needed and indeed used when working with shared memory DIBSECTIONs LONG CDrawImage::GetPaletteVersion() { return m_PaletteVersion; } // Resets the current palette version number void CDrawImage::ResetPaletteVersion() { m_PaletteVersion = PALETTE_VERSION; } // Increment the current palette version void CDrawImage::IncrementPaletteVersion() { m_PaletteVersion++; } // Constructor must initialise the base allocator. Each sample we create has a // palette version cookie on board. When the source filter changes the palette // during streaming the window object increments an internal cookie counter it // keeps as well. When it comes to render the samples it looks at the cookie // values and if they don't match then it knows to update the sample's colour // table. However we always create samples with a cookie of PALETTE_VERSION // If there have been multiple format changes and we disconnect and reconnect // thereby causing the samples to be reallocated we will create them with a // cookie much lower than the current version, this isn't a problem since it // will be seen by the window object and the versions will then be updated CImageAllocator::CImageAllocator(__inout CBaseFilter *pFilter, __in_opt LPCTSTR pName, __inout HRESULT *phr) : CBaseAllocator(pName,NULL,phr,TRUE,TRUE), m_pFilter(pFilter) { ASSERT(phr); ASSERT(pFilter); } // Check our DIB buffers have been released #ifdef DEBUG CImageAllocator::~CImageAllocator() { ASSERT(m_bCommitted == FALSE); } #endif // Called from destructor and also from base class to free resources. We work // our way through the list of media samples deleting the DIBSECTION created // for each. All samples should be back in our list so there is no chance a // filter is still using one to write on the display or hold on a pending list void CImageAllocator::Free() { ASSERT(m_lAllocated == m_lFree.GetCount()); EXECUTE_ASSERT(GdiFlush()); CImageSample *pSample; DIBDATA *pDibData; while (m_lFree.GetCount() != 0) { pSample = (CImageSample *) m_lFree.RemoveHead(); pDibData = pSample->GetDIBData(); EXECUTE_ASSERT(DeleteObject(pDibData->hBitmap)); EXECUTE_ASSERT(CloseHandle(pDibData->hMapping)); delete pSample; } m_lAllocated = 0; } // Prepare the allocator by checking all the input parameters STDMETHODIMP CImageAllocator::CheckSizes(__in ALLOCATOR_PROPERTIES *pRequest) { // Check we have a valid connection if (m_pMediaType == NULL) { return VFW_E_NOT_CONNECTED; } // NOTE We always create a DIB section with the source format type which // may contain a source palette. When we do the BitBlt drawing operation // the target display device may contain a different palette (we may not // have the focus) in which case GDI will do after the palette mapping VIDEOINFOHEADER *pVideoInfo = (VIDEOINFOHEADER *) m_pMediaType->Format(); // When we call CreateDIBSection it implicitly maps only enough memory // for the image as defined by thee BITMAPINFOHEADER. If the user asks // for an image smaller than this then we reject the call, if they ask // for an image larger than this then we return what they can have if ((DWORD) pRequest->cbBuffer < pVideoInfo->bmiHeader.biSizeImage) { return E_INVALIDARG; } // Reject buffer prefixes if (pRequest->cbPrefix > 0) { return E_INVALIDARG; } pRequest->cbBuffer = pVideoInfo->bmiHeader.biSizeImage; return NOERROR; } // Agree the number of media sample buffers and their sizes. The base class // this allocator is derived from allows samples to be aligned only on byte // boundaries NOTE the buffers are not allocated until the Commit call STDMETHODIMP CImageAllocator::SetProperties( __in ALLOCATOR_PROPERTIES * pRequest, __out ALLOCATOR_PROPERTIES * pActual) { ALLOCATOR_PROPERTIES Adjusted = *pRequest; // Check the parameters fit with the current connection HRESULT hr = CheckSizes(&Adjusted); if (FAILED(hr)) { return hr; } return CBaseAllocator::SetProperties(&Adjusted, pActual); } // Commit the memory by allocating the agreed number of media samples. For // each sample we are committed to creating we have a CImageSample object // that we use to manage it's resources. This is initialised with a DIBDATA // structure that contains amongst other things the GDI DIBSECTION handle // We will access the renderer media type during this so we must have locked // (to prevent the format changing for example). The class overrides Commit // and Decommit to do this locking (base class Commit in turn calls Alloc) HRESULT CImageAllocator::Alloc(void) { ASSERT(m_pMediaType); CImageSample *pSample; DIBDATA DibData; // Check the base allocator says it's ok to continue HRESULT hr = CBaseAllocator::Alloc(); if (FAILED(hr)) { return hr; } // We create a new memory mapped object although we don't map it into our // address space because GDI does that in CreateDIBSection. It is possible // that we run out of resources before creating all the samples in which // case the available sample list is left with those already created ASSERT(m_lAllocated == 0); while (m_lAllocated < m_lCount) { // Create and initialise a shared memory GDI buffer hr = CreateDIB(m_lSize,DibData); if (FAILED(hr)) { return hr; } // Create the sample object and pass it the DIBDATA pSample = CreateImageSample(DibData.pBase,m_lSize); if (pSample == NULL) { EXECUTE_ASSERT(DeleteObject(DibData.hBitmap)); EXECUTE_ASSERT(CloseHandle(DibData.hMapping)); return E_OUTOFMEMORY; } // Add the completed sample to the available list pSample->SetDIBData(&DibData); m_lFree.Add(pSample); m_lAllocated++; } return NOERROR; } // We have a virtual method that allocates the samples so that a derived class // may override it and allocate more specialised sample objects. So long as it // derives its samples from CImageSample then all this code will still work ok CImageSample *CImageAllocator::CreateImageSample(__in_bcount(Length) LPBYTE pData,LONG Length) { HRESULT hr = NOERROR; CImageSample *pSample; // Allocate the new sample and check the return codes pSample = new CImageSample((CBaseAllocator *) this, // Base class NAME("Video sample"), // DEBUG name (HRESULT *) &hr, // Return code (LPBYTE) pData, // DIB address (LONG) Length); // Size of DIB if (pSample == NULL || FAILED(hr)) { delete pSample; return NULL; } return pSample; } // This function allocates a shared memory block for use by the source filter // generating DIBs for us to render. The memory block is created in shared // memory so that GDI doesn't have to copy the memory when we do a BitBlt HRESULT CImageAllocator::CreateDIB(LONG InSize,DIBDATA &DibData) { BITMAPINFO *pbmi; // Format information for pin BYTE *pBase; // Pointer to the actual image HANDLE hMapping; // Handle to mapped object HBITMAP hBitmap; // DIB section bitmap handle // Create a file mapping object and map into our address space hMapping = CreateFileMapping(hMEMORY, // Use system page file NULL, // No security attributes PAGE_READWRITE, // Full access to memory (DWORD) 0, // Less than 4Gb in size InSize, // Size of buffer NULL); // No name to section if (hMapping == NULL) { DWORD Error = GetLastError(); return MAKE_HRESULT(SEVERITY_ERROR, FACILITY_WIN32, Error); } // NOTE We always create a DIB section with the source format type which // may contain a source palette. When we do the BitBlt drawing operation // the target display device may contain a different palette (we may not // have the focus) in which case GDI will do after the palette mapping pbmi = (BITMAPINFO *) HEADER(m_pMediaType->Format()); if (m_pMediaType == NULL) { DbgBreak("Invalid media type"); } hBitmap = CreateDIBSection((HDC) NULL, // NO device context pbmi, // Format information DIB_RGB_COLORS, // Use the palette (VOID **) &pBase, // Pointer to image data hMapping, // Mapped memory handle (DWORD) 0); // Offset into memory if (hBitmap == NULL || pBase == NULL) { EXECUTE_ASSERT(CloseHandle(hMapping)); DWORD Error = GetLastError(); return MAKE_HRESULT(SEVERITY_ERROR, FACILITY_WIN32, Error); } // Initialise the DIB information structure DibData.hBitmap = hBitmap; DibData.hMapping = hMapping; DibData.pBase = pBase; DibData.PaletteVersion = PALETTE_VERSION; GetObject(hBitmap,sizeof(DIBSECTION),(VOID *)&DibData.DibSection); return NOERROR; } // We use the media type during the DIBSECTION creation void CImageAllocator::NotifyMediaType(__in CMediaType *pMediaType) { m_pMediaType = pMediaType; } // Overriden to increment the owning object's reference count STDMETHODIMP_(ULONG) CImageAllocator::NonDelegatingAddRef() { return m_pFilter->AddRef(); } // Overriden to decrement the owning object's reference count STDMETHODIMP_(ULONG) CImageAllocator::NonDelegatingRelease() { return m_pFilter->Release(); } // If you derive a class from CMediaSample that has to transport specialised // member variables and entry points then there are three alternate solutions // The first is to create a memory buffer larger than actually required by the // sample and store your information either at the beginning of it or at the // end, the former being moderately safer allowing for misbehaving transform // filters. You then adjust the buffer address when you create the base media // sample. This has the disadvantage of breaking up the memory allocated to // the samples into separate blocks. The second solution is to implement a // class derived from CMediaSample and support additional interface(s) that // convey your private data. This means defining a custom interface. The final // alternative is to create a class that inherits from CMediaSample and adds // the private data structures, when you get an IMediaSample in your Receive() // call check to see if your allocator is being used, and if it is then cast // the IMediaSample into one of your objects. Additional checks can be made // to ensure the sample's this pointer is known to be one of your own objects CImageSample::CImageSample(__inout CBaseAllocator *pAllocator, __in_opt LPCTSTR pName, __inout HRESULT *phr, __in_bcount(length) LPBYTE pBuffer, LONG length) : CMediaSample(pName,pAllocator,phr,pBuffer,length), m_bInit(FALSE) { ASSERT(pAllocator); ASSERT(pBuffer); } // Set the shared memory DIB information void CImageSample::SetDIBData(__in DIBDATA *pDibData) { ASSERT(pDibData); m_DibData = *pDibData; m_bInit = TRUE; } // Retrieve the shared memory DIB data __out DIBDATA *CImageSample::GetDIBData() { ASSERT(m_bInit == TRUE); return &m_DibData; } // This class handles the creation of a palette. It is fairly specialist and // is intended to simplify palette management for video renderer filters. It // is for this reason that the constructor requires three other objects with // which it interacts, namely a base media filter, a base window and a base // drawing object although the base window or the draw object may be NULL to // ignore that part of us. We try not to create and install palettes unless // absolutely necessary as they typically require WM_PALETTECHANGED messages // to be sent to every window thread in the system which is very expensive CImagePalette::CImagePalette(__inout CBaseFilter *pBaseFilter, __inout CBaseWindow *pBaseWindow, __inout CDrawImage *pDrawImage) : m_pBaseWindow(pBaseWindow), m_pFilter(pBaseFilter), m_pDrawImage(pDrawImage), m_hPalette(NULL) { ASSERT(m_pFilter); } // Destructor #ifdef DEBUG CImagePalette::~CImagePalette() { ASSERT(m_hPalette == NULL); } #endif // We allow dynamic format changes of the palette but rather than change the // palette every time we call this to work out whether an update is required. // If the original type didn't use a palette and the new one does (or vica // versa) then we return TRUE. If neither formats use a palette we'll return // FALSE. If both formats use a palette we compare their colours and return // FALSE if they match. This therefore short circuits palette creation unless // absolutely necessary since installing palettes is an expensive operation BOOL CImagePalette::ShouldUpdate(const VIDEOINFOHEADER *pNewInfo, const VIDEOINFOHEADER *pOldInfo) { // We may not have a current format yet if (pOldInfo == NULL) { return TRUE; } // Do both formats not require a palette if (ContainsPalette(pNewInfo) == FALSE) { if (ContainsPalette(pOldInfo) == FALSE) { return FALSE; } } // Compare the colours to see if they match DWORD VideoEntries = pNewInfo->bmiHeader.biClrUsed; if (ContainsPalette(pNewInfo) == TRUE) if (ContainsPalette(pOldInfo) == TRUE) if (pOldInfo->bmiHeader.biClrUsed == VideoEntries) if (pOldInfo->bmiHeader.biClrUsed > 0) if (memcmp((PVOID) GetBitmapPalette(pNewInfo), (PVOID) GetBitmapPalette(pOldInfo), VideoEntries * sizeof(RGBQUAD)) == 0) { return FALSE; } return TRUE; } // This is normally called when the input pin type is set to install a palette // We will typically be called from two different places. The first is when we // have negotiated a palettised media type after connection, the other is when // we receive a new type during processing with an updated palette in which // case we must remove and release the resources held by the current palette // We can be passed an optional device name if we wish to prepare a palette // for a specific monitor on a multi monitor system HRESULT CImagePalette::PreparePalette(const CMediaType *pmtNew, const CMediaType *pmtOld, __in LPSTR szDevice) { const VIDEOINFOHEADER *pNewInfo = (VIDEOINFOHEADER *) pmtNew->Format(); const VIDEOINFOHEADER *pOldInfo = (VIDEOINFOHEADER *) pmtOld->Format(); ASSERT(pNewInfo); // This is an performance optimisation, when we get a media type we check // to see if the format requires a palette change. If either we need one // when previously we didn't or vica versa then this returns TRUE, if we // previously needed a palette and we do now it compares their colours if (ShouldUpdate(pNewInfo,pOldInfo) == FALSE) { NOTE("No update needed"); return S_FALSE; } // We must notify the filter graph that the application may have changed // the palette although in practice we don't bother checking to see if it // is really different. If it tries to get the palette either the window // or renderer lock will ensure it doesn't get in until we are finished RemovePalette(); m_pFilter->NotifyEvent(EC_PALETTE_CHANGED,0,0); // Do we need a palette for the new format if (ContainsPalette(pNewInfo) == FALSE) { NOTE("New has no palette"); return S_FALSE; } if (m_pBaseWindow) { m_pBaseWindow->LockPaletteLock(); } // If we're changing the palette on the fly then we increment our palette // cookie which is compared against the cookie also stored in all of our // DIBSECTION media samples. If they don't match when we come to draw it // then we know the sample is out of date and we'll update it's palette NOTE("Making new colour palette"); m_hPalette = MakePalette(pNewInfo, szDevice); ASSERT(m_hPalette != NULL); if (m_pBaseWindow) { m_pBaseWindow->UnlockPaletteLock(); } // The window in which the new palette is to be realised may be a NULL // pointer to signal that no window is in use, if so we don't call it // Some filters just want to use this object to create/manage palettes if (m_pBaseWindow) m_pBaseWindow->SetPalette(m_hPalette); // This is the only time where we need access to the draw object to say // to it that a new palette will be arriving on a sample real soon. The // constructor may take a NULL pointer in which case we don't call this if (m_pDrawImage) m_pDrawImage->IncrementPaletteVersion(); return NOERROR; } // Helper function to copy a palette out of any kind of VIDEOINFO (ie it may // be YUV or true colour) into a palettised VIDEOINFO. We use this changing // palettes on DirectDraw samples as a source filter can attach a palette to // any buffer (eg YUV) and hand it back. We make a new palette out of that // format and then copy the palette colours into the current connection type HRESULT CImagePalette::CopyPalette(const CMediaType *pSrc,__out CMediaType *pDest) { // Reset the destination palette before starting VIDEOINFOHEADER *pDestInfo = (VIDEOINFOHEADER *) pDest->Format(); pDestInfo->bmiHeader.biClrUsed = 0; pDestInfo->bmiHeader.biClrImportant = 0; // Does the destination have a palette if (PALETTISED(pDestInfo) == FALSE) { NOTE("No destination palette"); return S_FALSE; } // Does the source contain a palette const VIDEOINFOHEADER *pSrcInfo = (VIDEOINFOHEADER *) pSrc->Format(); if (ContainsPalette(pSrcInfo) == FALSE) { NOTE("No source palette"); return S_FALSE; } // The number of colours may be zero filled DWORD PaletteEntries = pSrcInfo->bmiHeader.biClrUsed; if (PaletteEntries == 0) { DWORD Maximum = (1 << pSrcInfo->bmiHeader.biBitCount); NOTE1("Setting maximum colours (%d)",Maximum); PaletteEntries = Maximum; } // Make sure the destination has enough room for the palette ASSERT(pSrcInfo->bmiHeader.biClrUsed <= iPALETTE_COLORS); ASSERT(pSrcInfo->bmiHeader.biClrImportant <= PaletteEntries); ASSERT(COLORS(pDestInfo) == GetBitmapPalette(pDestInfo)); pDestInfo->bmiHeader.biClrUsed = PaletteEntries; pDestInfo->bmiHeader.biClrImportant = pSrcInfo->bmiHeader.biClrImportant; ULONG BitmapSize = GetBitmapFormatSize(HEADER(pSrcInfo)); if (pDest->FormatLength() < BitmapSize) { NOTE("Reallocating destination"); pDest->ReallocFormatBuffer(BitmapSize); } // Now copy the palette colours across CopyMemory((PVOID) COLORS(pDestInfo), (PVOID) GetBitmapPalette(pSrcInfo), PaletteEntries * sizeof(RGBQUAD)); return NOERROR; } // This is normally called when the palette is changed (typically during a // dynamic format change) to remove any palette we previously installed. We // replace it (if necessary) in the video window with a standard VGA palette // that should always be available even if this is a true colour display HRESULT CImagePalette::RemovePalette() { if (m_pBaseWindow) { m_pBaseWindow->LockPaletteLock(); } // Do we have a palette to remove if (m_hPalette != NULL) { if (m_pBaseWindow) { // Make sure that the window's palette handle matches // our palette handle. ASSERT(m_hPalette == m_pBaseWindow->GetPalette()); m_pBaseWindow->UnsetPalette(); } EXECUTE_ASSERT(DeleteObject(m_hPalette)); m_hPalette = NULL; } if (m_pBaseWindow) { m_pBaseWindow->UnlockPaletteLock(); } return NOERROR; } // Called to create a palette for the object, the data structure used by GDI // to describe a palette is a LOGPALETTE, this includes a variable number of // PALETTEENTRY fields which are the colours, we have to convert the RGBQUAD // colour fields we are handed in a BITMAPINFO from the media type into these // This handles extraction of palettes from true colour and YUV media formats // We can be passed an optional device name if we wish to prepare a palette // for a specific monitor on a multi monitor system HPALETTE CImagePalette::MakePalette(const VIDEOINFOHEADER *pVideoInfo, __in LPSTR szDevice) { ASSERT(ContainsPalette(pVideoInfo) == TRUE); ASSERT(pVideoInfo->bmiHeader.biClrUsed <= iPALETTE_COLORS); BITMAPINFOHEADER *pHeader = HEADER(pVideoInfo); const RGBQUAD *pColours; // Pointer to the palette LOGPALETTE *lp; // Used to create a palette HPALETTE hPalette; // Logical palette object lp = (LOGPALETTE *) new BYTE[sizeof(LOGPALETTE) + SIZE_PALETTE]; if (lp == NULL) { return NULL; } // Unfortunately for some hare brained reason a GDI palette entry (a // PALETTEENTRY structure) is different to a palette entry from a DIB // format (a RGBQUAD structure) so we have to do the field conversion // The VIDEOINFO containing the palette may be a true colour type so // we use GetBitmapPalette to skip over any bit fields if they exist lp->palVersion = PALVERSION; lp->palNumEntries = (USHORT) pHeader->biClrUsed; if (lp->palNumEntries == 0) lp->palNumEntries = (1 << pHeader->biBitCount); pColours = GetBitmapPalette(pVideoInfo); for (DWORD dwCount = 0;dwCount < lp->palNumEntries;dwCount++) { lp->palPalEntry[dwCount].peRed = pColours[dwCount].rgbRed; lp->palPalEntry[dwCount].peGreen = pColours[dwCount].rgbGreen; lp->palPalEntry[dwCount].peBlue = pColours[dwCount].rgbBlue; lp->palPalEntry[dwCount].peFlags = 0; } MakeIdentityPalette(lp->palPalEntry, lp->palNumEntries, szDevice); // Create a logical palette hPalette = CreatePalette(lp); ASSERT(hPalette != NULL); delete[] lp; return hPalette; } // GDI does a fair job of compressing the palette entries you give it, so for // example if you have five entries with an RGB colour (0,0,0) it will remove // all but one of them. When you subsequently draw an image it will map from // your logical palette to the compressed device palette. This function looks // to see if it is trying to be an identity palette and if so sets the flags // field in the PALETTEENTRYs so they remain expanded to boost performance // We can be passed an optional device name if we wish to prepare a palette // for a specific monitor on a multi monitor system HRESULT CImagePalette::MakeIdentityPalette(__inout_ecount_full(iColours) PALETTEENTRY *pEntry,INT iColours, __in LPSTR szDevice) { PALETTEENTRY SystemEntries[10]; // System palette entries BOOL bIdentityPalette = TRUE; // Is an identity palette ASSERT(iColours <= iPALETTE_COLORS); // Should have a palette const int PalLoCount = 10; // First ten reserved colours const int PalHiStart = 246; // Last VGA palette entries // Does this have the full colour range if (iColours < 10) { return S_FALSE; } // Apparently some displays have odd numbers of system colours // Get a DC on the right monitor - it's ugly, but this is the way you have // to do it HDC hdc; if (szDevice == NULL || lstrcmpiLocaleIndependentA(szDevice, "DISPLAY") == 0) hdc = CreateDCA("DISPLAY", NULL, NULL, NULL); else hdc = CreateDCA(NULL, szDevice, NULL, NULL); if (NULL == hdc) { return E_OUTOFMEMORY; } INT Reserved = GetDeviceCaps(hdc,NUMRESERVED); if (Reserved != 20) { DeleteDC(hdc); return S_FALSE; } // Compare our palette against the first ten system entries. The reason I // don't do a memory compare between our two arrays of colours is because // I am not sure what will be in the flags fields for the system entries UINT Result = GetSystemPaletteEntries(hdc,0,PalLoCount,SystemEntries); for (UINT Count = 0;Count < Result;Count++) { if (SystemEntries[Count].peRed != pEntry[Count].peRed || SystemEntries[Count].peGreen != pEntry[Count].peGreen || SystemEntries[Count].peBlue != pEntry[Count].peBlue) { bIdentityPalette = FALSE; } } // And likewise compare against the last ten entries Result = GetSystemPaletteEntries(hdc,PalHiStart,PalLoCount,SystemEntries); for (UINT Count = 0;Count < Result;Count++) { if (INT(Count) + PalHiStart < iColours) { if (SystemEntries[Count].peRed != pEntry[PalHiStart + Count].peRed || SystemEntries[Count].peGreen != pEntry[PalHiStart + Count].peGreen || SystemEntries[Count].peBlue != pEntry[PalHiStart + Count].peBlue) { bIdentityPalette = FALSE; } } } // If not an identity palette then return S_FALSE DeleteDC(hdc); if (bIdentityPalette == FALSE) { return S_FALSE; } // Set the non VGA entries so that GDI doesn't map them for (UINT Count = PalLoCount;INT(Count) < min(PalHiStart,iColours);Count++) { pEntry[Count].peFlags = PC_NOCOLLAPSE; } return NOERROR; } // Constructor initialises the VIDEOINFO we keep storing the current display // format. The format can be changed at any time, to reset the format held // by us call the RefreshDisplayType directly (it's a public method). Since // more than one thread will typically call us (ie window threads resetting // the type and source threads in the type checking methods) we have a lock CImageDisplay::CImageDisplay() { RefreshDisplayType(NULL); } // This initialises the format we hold which contains the display device type // We do a conversion on the display device type in here so that when we start // type checking input formats we can assume that certain fields have been set // correctly, an example is when we make the 16 bit mask fields explicit. This // is normally called when we receive WM_DEVMODECHANGED device change messages // The optional szDeviceName parameter tells us which monitor we are interested // in for a multi monitor system HRESULT CImageDisplay::RefreshDisplayType(__in_opt LPSTR szDeviceName) { CAutoLock cDisplayLock(this); // Set the preferred format type ZeroMemory((PVOID)&m_Display,sizeof(VIDEOINFOHEADER)+sizeof(TRUECOLORINFO)); m_Display.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); m_Display.bmiHeader.biBitCount = FALSE; // Get the bit depth of a device compatible bitmap // get caps of whichever monitor they are interested in (multi monitor) HDC hdcDisplay; // it's ugly, but this is the way you have to do it if (szDeviceName == NULL || lstrcmpiLocaleIndependentA(szDeviceName, "DISPLAY") == 0) hdcDisplay = CreateDCA("DISPLAY", NULL, NULL, NULL); else hdcDisplay = CreateDCA(NULL, szDeviceName, NULL, NULL); if (hdcDisplay == NULL) { ASSERT(FALSE); DbgLog((LOG_ERROR,1,TEXT("ACK! Can't get a DC for %hs"), szDeviceName ? szDeviceName : "")); return E_FAIL; } else { DbgLog((LOG_TRACE,3,TEXT("Created a DC for %s"), szDeviceName ? szDeviceName : "")); } HBITMAP hbm = CreateCompatibleBitmap(hdcDisplay,1,1); if ( hbm ) { GetDIBits(hdcDisplay,hbm,0,1,NULL,(BITMAPINFO *)&m_Display.bmiHeader,DIB_RGB_COLORS); // This call will get the colour table or the proper bitfields GetDIBits(hdcDisplay,hbm,0,1,NULL,(BITMAPINFO *)&m_Display.bmiHeader,DIB_RGB_COLORS); DeleteObject(hbm); } DeleteDC(hdcDisplay); // Complete the display type initialisation ASSERT(CheckHeaderValidity(&m_Display)); UpdateFormat(&m_Display); DbgLog((LOG_TRACE,3,TEXT("New DISPLAY bit depth =%d"), m_Display.bmiHeader.biBitCount)); return NOERROR; } // We assume throughout this code that any bitfields masks are allowed no // more than eight bits to store a colour component. This checks that the // bit count assumption is enforced and also makes sure that all the bits // set are contiguous. We return a boolean TRUE if the field checks out ok BOOL CImageDisplay::CheckBitFields(const VIDEOINFO *pInput) { DWORD *pBitFields = (DWORD *) BITMASKS(pInput); for (INT iColour = iRED;iColour <= iBLUE;iColour++) { // First of all work out how many bits are set DWORD SetBits = CountSetBits(pBitFields[iColour]); if (SetBits > iMAXBITS || SetBits == 0) { NOTE1("Bit fields for component %d invalid",iColour); return FALSE; } // Next work out the number of zero bits prefix DWORD PrefixBits = CountPrefixBits(pBitFields[iColour]); // This is going to see if all the bits set are contiguous (as they // should be). We know how much to shift them right by from the // count of prefix bits. The number of bits set defines a mask, we // invert this (ones complement) and AND it with the shifted bit // fields. If the result is NON zero then there are bit(s) sticking // out the left hand end which means they are not contiguous DWORD TestField = pBitFields[iColour] >> PrefixBits; DWORD Mask = ULONG_MAX << SetBits; if (TestField & Mask) { NOTE1("Bit fields for component %d not contiguous",iColour); return FALSE; } } return TRUE; } // This counts the number of bits set in the input field DWORD CImageDisplay::CountSetBits(DWORD Field) { // This is a relatively well known bit counting algorithm DWORD Count = 0; DWORD init = Field; // Until the input is exhausted, count the number of bits while (init) { init = init & (init - 1); // Turn off the bottommost bit Count++; } return Count; } // This counts the number of zero bits upto the first one set NOTE the input // field should have been previously checked to ensure there is at least one // set although if we don't find one set we return the impossible value 32 DWORD CImageDisplay::CountPrefixBits(DWORD Field) { DWORD Mask = 1; DWORD Count = 0; while (TRUE) { if (Field & Mask) { return Count; } Count++; ASSERT(Mask != 0x80000000); if (Mask == 0x80000000) { return Count; } Mask <<= 1; } } // This is called to check the BITMAPINFOHEADER for the input type. There are // many implicit dependancies between the fields in a header structure which // if we validate now make for easier manipulation in subsequent handling. We // also check that the BITMAPINFOHEADER matches it's specification such that // fields likes the number of planes is one, that it's structure size is set // correctly and that the bitmap dimensions have not been set as negative BOOL CImageDisplay::CheckHeaderValidity(const VIDEOINFO *pInput) { // Check the bitmap width and height are not negative. if (pInput->bmiHeader.biWidth <= 0 || pInput->bmiHeader.biHeight <= 0) { NOTE("Invalid bitmap dimensions"); return FALSE; } // Check the compression is either BI_RGB or BI_BITFIELDS if (pInput->bmiHeader.biCompression != BI_RGB) { if (pInput->bmiHeader.biCompression != BI_BITFIELDS) { NOTE("Invalid compression format"); return FALSE; } } // If BI_BITFIELDS compression format check the colour depth if (pInput->bmiHeader.biCompression == BI_BITFIELDS) { if (pInput->bmiHeader.biBitCount != 16) { if (pInput->bmiHeader.biBitCount != 32) { NOTE("BI_BITFIELDS not 16/32 bit depth"); return FALSE; } } } // Check the assumptions about the layout of the bit fields if (pInput->bmiHeader.biCompression == BI_BITFIELDS) { if (CheckBitFields(pInput) == FALSE) { NOTE("Bit fields are not valid"); return FALSE; } } // Are the number of planes equal to one if (pInput->bmiHeader.biPlanes != 1) { NOTE("Number of planes not one"); return FALSE; } // Check the image size is consistent (it can be zero) if (pInput->bmiHeader.biSizeImage != GetBitmapSize(&pInput->bmiHeader)) { if (pInput->bmiHeader.biSizeImage) { NOTE("Image size incorrectly set"); return FALSE; } } // Check the size of the structure if (pInput->bmiHeader.biSize != sizeof(BITMAPINFOHEADER)) { NOTE("Size of BITMAPINFOHEADER wrong"); return FALSE; } return CheckPaletteHeader(pInput); } // This runs a few simple tests against the palette fields in the input to // see if it looks vaguely correct. The tests look at the number of palette // colours present, the number considered important and the biCompression // field which should always be BI_RGB as no other formats are meaningful BOOL CImageDisplay::CheckPaletteHeader(const VIDEOINFO *pInput) { // The checks here are for palettised videos only if (PALETTISED(pInput) == FALSE) { if (pInput->bmiHeader.biClrUsed) { NOTE("Invalid palette entries"); return FALSE; } return TRUE; } // Compression type of BI_BITFIELDS is meaningless for palette video if (pInput->bmiHeader.biCompression != BI_RGB) { NOTE("Palettised video must be BI_RGB"); return FALSE; } // Check the number of palette colours is correct if (pInput->bmiHeader.biClrUsed > PALETTE_ENTRIES(pInput)) { NOTE("Too many colours in palette"); return FALSE; } // The number of important colours shouldn't exceed the number used if (pInput->bmiHeader.biClrImportant > pInput->bmiHeader.biClrUsed) { NOTE("Too many important colours"); return FALSE; } return TRUE; } // Return the format of the video display const VIDEOINFO *CImageDisplay::GetDisplayFormat() { return &m_Display; } // Return TRUE if the display uses a palette BOOL CImageDisplay::IsPalettised() { return PALETTISED(&m_Display); } // Return the bit depth of the current display setting WORD CImageDisplay::GetDisplayDepth() { return m_Display.bmiHeader.biBitCount; } // Initialise the optional fields in a VIDEOINFO. These are mainly to do with // the source and destination rectangles and palette information such as the // number of colours present. It simplifies our code just a little if we don't // have to keep checking for all the different valid permutations in a header // every time we want to do anything with it (an example would be creating a // palette). We set the base class media type before calling this function so // that the media types between the pins match after a connection is made HRESULT CImageDisplay::UpdateFormat(__inout VIDEOINFO *pVideoInfo) { ASSERT(pVideoInfo); BITMAPINFOHEADER *pbmi = HEADER(pVideoInfo); SetRectEmpty(&pVideoInfo->rcSource); SetRectEmpty(&pVideoInfo->rcTarget); // Set the number of colours explicitly if (PALETTISED(pVideoInfo)) { if (pVideoInfo->bmiHeader.biClrUsed == 0) { pVideoInfo->bmiHeader.biClrUsed = PALETTE_ENTRIES(pVideoInfo); } } // The number of important colours shouldn't exceed the number used, on // some displays the number of important colours is not initialised when // retrieving the display type so we set the colours used correctly if (pVideoInfo->bmiHeader.biClrImportant > pVideoInfo->bmiHeader.biClrUsed) { pVideoInfo->bmiHeader.biClrImportant = PALETTE_ENTRIES(pVideoInfo); } // Change the image size field to be explicit if (pVideoInfo->bmiHeader.biSizeImage == 0) { pVideoInfo->bmiHeader.biSizeImage = GetBitmapSize(&pVideoInfo->bmiHeader); } return NOERROR; } // Lots of video rendering filters want code to check proposed formats are ok // This checks the VIDEOINFO we are passed as a media type. If the media type // is a valid media type then we return NOERROR otherwise E_INVALIDARG. Note // however we only accept formats that can be easily displayed in the display // so if we are on a 16 bit device we will not accept 24 bit images. The one // complexity is that most displays draw 8 bit palettised images efficiently // Also if the input format is less colour bits per pixel then we also accept HRESULT CImageDisplay::CheckVideoType(const VIDEOINFO *pInput) { // First of all check the VIDEOINFOHEADER looks correct if (CheckHeaderValidity(pInput) == FALSE) { return E_INVALIDARG; } // Virtually all devices support palettised images efficiently if (m_Display.bmiHeader.biBitCount == pInput->bmiHeader.biBitCount) { if (PALETTISED(pInput) == TRUE) { ASSERT(PALETTISED(&m_Display) == TRUE); NOTE("(Video) Type connection ACCEPTED"); return NOERROR; } } // Is the display depth greater than the input format if (m_Display.bmiHeader.biBitCount > pInput->bmiHeader.biBitCount) { NOTE("(Video) Mismatch agreed"); return NOERROR; } // Is the display depth less than the input format if (m_Display.bmiHeader.biBitCount < pInput->bmiHeader.biBitCount) { NOTE("(Video) Format mismatch"); return E_INVALIDARG; } // Both input and display formats are either BI_RGB or BI_BITFIELDS ASSERT(m_Display.bmiHeader.biBitCount == pInput->bmiHeader.biBitCount); ASSERT(PALETTISED(pInput) == FALSE); ASSERT(PALETTISED(&m_Display) == FALSE); // BI_RGB 16 bit representation is implicitly RGB555, and likewise BI_RGB // 24 bit representation is RGB888. So we initialise a pointer to the bit // fields they really mean and check against the display device format // This is only going to be called when both formats are equal bits pixel const DWORD *pInputMask = GetBitMasks(pInput); const DWORD *pDisplayMask = GetBitMasks((VIDEOINFO *)&m_Display); if (pInputMask[iRED] != pDisplayMask[iRED] || pInputMask[iGREEN] != pDisplayMask[iGREEN] || pInputMask[iBLUE] != pDisplayMask[iBLUE]) { NOTE("(Video) Bit field mismatch"); return E_INVALIDARG; } NOTE("(Video) Type connection ACCEPTED"); return NOERROR; } // Return the bit masks for the true colour VIDEOINFO provided const DWORD *CImageDisplay::GetBitMasks(const VIDEOINFO *pVideoInfo) { static const DWORD FailMasks[] = {0,0,0}; if (pVideoInfo->bmiHeader.biCompression == BI_BITFIELDS) { return BITMASKS(pVideoInfo); } ASSERT(pVideoInfo->bmiHeader.biCompression == BI_RGB); switch (pVideoInfo->bmiHeader.biBitCount) { case 16: return bits555; case 24: return bits888; case 32: return bits888; default: return FailMasks; } } // Check to see if we can support media type pmtIn as proposed by the output // pin - We first check that the major media type is video and also identify // the media sub type. Then we thoroughly check the VIDEOINFO type provided // As well as the contained VIDEOINFO being correct the major type must be // video, the subtype a recognised video format and the type GUID correct HRESULT CImageDisplay::CheckMediaType(const CMediaType *pmtIn) { // Does this have a VIDEOINFOHEADER format block const GUID *pFormatType = pmtIn->FormatType(); if (*pFormatType != FORMAT_VideoInfo) { NOTE("Format GUID not a VIDEOINFOHEADER"); return E_INVALIDARG; } ASSERT(pmtIn->Format()); // Check the format looks reasonably ok ULONG Length = pmtIn->FormatLength(); if (Length < SIZE_VIDEOHEADER) { NOTE("Format smaller than a VIDEOHEADER"); return E_FAIL; } VIDEOINFO *pInput = (VIDEOINFO *) pmtIn->Format(); // Check the major type is MEDIATYPE_Video const GUID *pMajorType = pmtIn->Type(); if (*pMajorType != MEDIATYPE_Video) { NOTE("Major type not MEDIATYPE_Video"); return E_INVALIDARG; } // Check we can identify the media subtype const GUID *pSubType = pmtIn->Subtype(); if (GetBitCount(pSubType) == USHRT_MAX) { NOTE("Invalid video media subtype"); return E_INVALIDARG; } return CheckVideoType(pInput); } // Given a video format described by a VIDEOINFO structure we return the mask // that is used to obtain the range of acceptable colours for this type, for // example, the mask for a 24 bit true colour format is 0xFF in all cases. A // 16 bit 5:6:5 display format uses 0xF8, 0xFC and 0xF8, therefore given any // RGB triplets we can AND them with these fields to find one that is valid BOOL CImageDisplay::GetColourMask(__out DWORD *pMaskRed, __out DWORD *pMaskGreen, __out DWORD *pMaskBlue) { CAutoLock cDisplayLock(this); *pMaskRed = 0xFF; *pMaskGreen = 0xFF; *pMaskBlue = 0xFF; // If this format is palettised then it doesn't have bit fields if (m_Display.bmiHeader.biBitCount < 16) { return FALSE; } // If this is a 24 bit true colour display then it can handle all the // possible colour component ranges described by a byte. It is never // allowed for a 24 bit colour depth image to have BI_BITFIELDS set if (m_Display.bmiHeader.biBitCount == 24) { ASSERT(m_Display.bmiHeader.biCompression == BI_RGB); return TRUE; } // Calculate the mask based on the format's bit fields const DWORD *pBitFields = (DWORD *) GetBitMasks((VIDEOINFO *)&m_Display); DWORD *pOutputMask[] = { pMaskRed, pMaskGreen, pMaskBlue }; // We know from earlier testing that there are no more than iMAXBITS // bits set in the mask and that they are all contiguous. All that // therefore remains is to shift them into the correct position for (INT iColour = iRED;iColour <= iBLUE;iColour++) { // This works out how many bits there are and where they live DWORD PrefixBits = CountPrefixBits(pBitFields[iColour]); DWORD SetBits = CountSetBits(pBitFields[iColour]); // The first shift moves the bit field so that it is right justified // in the DWORD, after which we then shift it back left which then // puts the leading bit in the bytes most significant bit position *(pOutputMask[iColour]) = pBitFields[iColour] >> PrefixBits; *(pOutputMask[iColour]) <<= (iMAXBITS - SetBits); } return TRUE; } /* Helper to convert to VIDEOINFOHEADER2 */ STDAPI ConvertVideoInfoToVideoInfo2(__inout AM_MEDIA_TYPE *pmt) { if (pmt->formattype != FORMAT_VideoInfo) { return E_INVALIDARG; } if (NULL == pmt->pbFormat || pmt->cbFormat < sizeof(VIDEOINFOHEADER)) { return E_INVALIDARG; } VIDEOINFO *pVideoInfo = (VIDEOINFO *)pmt->pbFormat; DWORD dwNewSize; HRESULT hr = DWordAdd(pmt->cbFormat, sizeof(VIDEOINFOHEADER2) - sizeof(VIDEOINFOHEADER), &dwNewSize); if (FAILED(hr)) { return hr; } PVOID pvNew = CoTaskMemAlloc(dwNewSize); if (pvNew == NULL) { return E_OUTOFMEMORY; } CopyMemory(pvNew, pmt->pbFormat, FIELD_OFFSET(VIDEOINFOHEADER, bmiHeader)); ZeroMemory((PBYTE)pvNew + FIELD_OFFSET(VIDEOINFOHEADER, bmiHeader), sizeof(VIDEOINFOHEADER2) - sizeof(VIDEOINFOHEADER)); CopyMemory((PBYTE)pvNew + FIELD_OFFSET(VIDEOINFOHEADER2, bmiHeader), pmt->pbFormat + FIELD_OFFSET(VIDEOINFOHEADER, bmiHeader), pmt->cbFormat - FIELD_OFFSET(VIDEOINFOHEADER, bmiHeader)); VIDEOINFOHEADER2 *pVideoInfo2 = (VIDEOINFOHEADER2 *)pvNew; pVideoInfo2->dwPictAspectRatioX = (DWORD)pVideoInfo2->bmiHeader.biWidth; pVideoInfo2->dwPictAspectRatioY = (DWORD)abs(pVideoInfo2->bmiHeader.biHeight); pmt->formattype = FORMAT_VideoInfo2; CoTaskMemFree(pmt->pbFormat); pmt->pbFormat = (PBYTE)pvNew; pmt->cbFormat += sizeof(VIDEOINFOHEADER2) - sizeof(VIDEOINFOHEADER); return S_OK; } // Check a media type containing VIDEOINFOHEADER STDAPI CheckVideoInfoType(const AM_MEDIA_TYPE *pmt) { if (NULL == pmt || NULL == pmt->pbFormat) { return E_POINTER; } if (pmt->majortype != MEDIATYPE_Video || pmt->formattype != FORMAT_VideoInfo || pmt->cbFormat < sizeof(VIDEOINFOHEADER)) { return VFW_E_TYPE_NOT_ACCEPTED; } const VIDEOINFOHEADER *pHeader = (const VIDEOINFOHEADER *)pmt->pbFormat; if (!ValidateBitmapInfoHeader( &pHeader->bmiHeader, pmt->cbFormat - FIELD_OFFSET(VIDEOINFOHEADER, bmiHeader))) { return VFW_E_TYPE_NOT_ACCEPTED; } return S_OK; } // Check a media type containing VIDEOINFOHEADER2 STDAPI CheckVideoInfo2Type(const AM_MEDIA_TYPE *pmt) { if (NULL == pmt || NULL == pmt->pbFormat) { return E_POINTER; } if (pmt->majortype != MEDIATYPE_Video || pmt->formattype != FORMAT_VideoInfo2 || pmt->cbFormat < sizeof(VIDEOINFOHEADER2)) { return VFW_E_TYPE_NOT_ACCEPTED; } const VIDEOINFOHEADER2 *pHeader = (const VIDEOINFOHEADER2 *)pmt->pbFormat; if (!ValidateBitmapInfoHeader( &pHeader->bmiHeader, pmt->cbFormat - FIELD_OFFSET(VIDEOINFOHEADER2, bmiHeader))) { return VFW_E_TYPE_NOT_ACCEPTED; } return S_OK; }