2751 lines
		
	
	
		
			94 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
		
		
			
		
	
	
			2751 lines
		
	
	
		
			94 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
|  | //------------------------------------------------------------------------------
 | ||
|  | // File: WinUtil.cpp
 | ||
|  | //
 | ||
|  | // Desc: DirectShow base classes - implements generic window handler class.
 | ||
|  | //
 | ||
|  | // Copyright (c) 1992-2001 Microsoft Corporation.  All rights reserved.
 | ||
|  | //------------------------------------------------------------------------------
 | ||
|  | 
 | ||
|  | #undef NOMINMAX
 | ||
|  | #include <streams.h>
 | ||
|  | #include <limits.h>
 | ||
|  | #include <dvdmedia.h>
 | ||
|  | #include <strsafe.h>
 | ||
|  | #include <checkbmi.h>
 | ||
|  | 
 | ||
|  | #ifndef max
 | ||
|  | #define max(a, b) (((a) > (b)) ? (a) : (b))
 | ||
|  | #endif
 | ||
|  | 
 | ||
|  | #ifndef min
 | ||
|  | #define min(a, b) (((a) < (b)) ? (a) : (b))
 | ||
|  | #endif
 | ||
|  | 
 | ||
|  | 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 = (CBaseWindow *)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, (LONG_PTR) 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 : "<NULL>")); | ||
|  |     return E_FAIL; | ||
|  |     } else { | ||
|  |     DbgLog((LOG_TRACE,3,TEXT("Created a DC for %s"), | ||
|  |                 szDeviceName ? szDeviceName : "<NULL>")); | ||
|  |     } | ||
|  |     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; | ||
|  | } |