topical media & game development

talk show tell print

hush-src-multi-BaseClasses-winutil.cpp / cpp



  //------------------------------------------------------------------------------
  // File: WinUtil.cpp
  //
  // Desc: DirectShow base classes - implements generic window handler class.
  //
  // Copyright (c)  Microsoft Corporation.  All rights reserved.
  //------------------------------------------------------------------------------
  
  include <streams.h>
  include <limits.h>
  include <dvdmedia.h>
  
  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)) {
  
              if (m_bDoPostToDestroy) {
  
                  CAMEvent m_evDone;
  
                  //  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(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
  
      wsprintf(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,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,
      LPRECT lprcSrc,
      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(RECT *pTargetRect)
  {
      ASSERT(pTargetRect);
      m_TargetRect = *pTargetRect;
      SetStretchMode();
  }
  
  // Return the current target rectangle
  
  void CDrawImage::GetTargetRect(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(RECT *pSourceRect)
  {
      ASSERT(pSourceRect);
      m_SourceRect = *pSourceRect;
      SetStretchMode();
  }
  
  // Return the current source rectangle
  
  void CDrawImage::GetSourceRect(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(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(CBaseFilter *pFilter,
                                   TCHAR *pName,
                                   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(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(
      ALLOCATOR_PROPERTIES * pRequest,
      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
  
          HRESULT 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(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(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(CBaseAllocator *pAllocator,
                             TCHAR *pName,
                             HRESULT *phr,
                             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(DIBDATA *pDibData)
  {
      ASSERT(pDibData);
      m_DibData = *pDibData;
      m_bInit = TRUE;
  }
  
  // Retrieve the shared memory DIB data
  
  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(CBaseFilter *pBaseFilter,
                               CBaseWindow *pBaseWindow,
                               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,
                                        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,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, 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(PALETTEENTRY *pEntry,INT iColours, 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 || lstrcmpiA(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 (int 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 (int 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(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 || lstrcmpiA(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(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(DWORD *pMaskRed,
                                    DWORD *pMaskGreen,
                                    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(AM_MEDIA_TYPE *pmt)
  {
      ASSERT(pmt->formattype == FORMAT_VideoInfo);
      VIDEOINFO *pVideoInfo = (VIDEOINFO *)pmt->pbFormat;
      PVOID pvNew = CoTaskMemAlloc(pmt->cbFormat + sizeof(VIDEOINFOHEADER2) -
                                   sizeof(VIDEOINFOHEADER));
      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)pVideoInfo2->bmiHeader.biHeight;
      pmt->formattype = FORMAT_VideoInfo2;
      CoTaskMemFree(pmt->pbFormat);
      pmt->pbFormat = (PBYTE)pvNew;
      pmt->cbFormat += sizeof(VIDEOINFOHEADER2) - sizeof(VIDEOINFOHEADER);
      return S_OK;
  }
  


(C) Æliens 20/2/2008

You may not copy or print any of this material without explicit permission of the author or the publisher. In case of other copyright issues, contact the author.