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