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