//------------------------------------------------------------------------------

// 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>



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()

{

    //

    // Before doing anything, check that someone has not already killed the

    // Video Renderer window.  If it has been killed we need to tidy up

    // a DC that the window was using.  If we don't do this check

    // the following GetWindowThreadProcessId test fails, but the SendMessage

    // goes nowhere and we leak the DC.

    //

    if (!IsWindow(m_hwnd)) {



        //

        // 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;

    }





    if (GetWindowThreadProcessId(m_hwnd, NULL) != GetCurrentThreadId()) {



        if (m_bDoPostToDestroy) {



            CAMEvent m_evDone;



            //  We must post a message to destroy the window

            //  That way we can't be in the middle of processing a

            //  message posted to our window when we do go away

            //  Sending a message gives less synchronization.

            PostMessage(m_hwnd, MsgDestroy, (WPARAM)(HANDLE)m_evDone, 0);

            WaitDispatchingMessages(m_evDone, INFINITE);

        } else {

            SendMessage(m_hwnd, MsgDestroy, 0, 0);

        }

        return NOERROR;

    }

    const HWND hwnd = m_hwnd;

    if (hwnd == NULL) {

        return NOERROR;

    }



    InactivateWindow();

    NOTE("Inactivated");



    // Reset the window styles before destruction



    SetWindowLong(hwnd,GWL_STYLE,m_WindowStyles);

    ASSERT(GetParent(hwnd) == NULL);

    NOTE1("Reset window styles %d",m_WindowStyles);



    //  UnintialiseWindow sets m_hwnd to NULL so save a copy

    UninitialiseWindow();

    DbgLog((LOG_TRACE, 2, TEXT("Destroying 0x%8.8X"), hwnd));

    if (!DestroyWindow(hwnd)) {

        DbgLog((LOG_TRACE, 0, TEXT("DestroyWindow %8.8X failed code %d"),

                hwnd, GetLastError()));

        DbgBreak("");

    }



    // Reset our state so we can be prepared again



    m_pClassName = NULL;

    m_ClassStyles = 0;

    m_WindowStyles = 0;

    m_WindowStylesEx = 0;

    m_ShowStageMessage = 0;

    m_ShowStageTop = 0;



    return NOERROR;

}





// Called at the end to put the window in an inactive state. The pending list

// will always have been cleared by this time so event if the worker thread

// gets has been signaled and gets in to render something it will find both

// the state has been changed and that there are no available sample images

// Since we wait on the window thread to complete we don't lock the object



HRESULT CBaseWindow::InactivateWindow()

{

    // Has the window been activated

    if (m_bActivated == FALSE) {

        return S_FALSE;

    }



    m_bActivated = FALSE;

    ShowWindow(m_hwnd,SW_HIDE);

    return NOERROR;

}





HRESULT CBaseWindow::CompleteConnect()

{

    m_bActivated = FALSE;

    return NOERROR;

}



// This displays a normal window. We ask the base window class for default

// sizes which unless overriden will return DEFWIDTH and DEFHEIGHT. We go

// through a couple of extra hoops to get the client area the right size

// as the object specifies which accounts for the AdjustWindowRectEx calls

// We also DWORD align the left and top coordinates of the window here to

// maximise the chance of being able to use DCI/DirectDraw primary surface



HRESULT CBaseWindow::ActivateWindow()

{

    // Has the window been sized and positioned already



    if (m_bActivated == TRUE || GetParent(m_hwnd) != NULL) {



        SetWindowPos(m_hwnd,            // Our window handle

                     HWND_TOP,          // Put it at the top

                     0, 0, 0, 0,        // Leave in current position

                     SWP_NOMOVE |       // Don't change it's place

                     SWP_NOSIZE);       // Change Z-order only



        m_bActivated = TRUE;

        return S_FALSE;

    }



    // Calculate the desired client rectangle



    RECT WindowRect, ClientRect = GetDefaultRect();

    GetWindowRect(m_hwnd,&WindowRect);

    AdjustWindowRectEx(&ClientRect,GetWindowLong(m_hwnd,GWL_STYLE),

                       FALSE,GetWindowLong(m_hwnd,GWL_EXSTYLE));



    // Align left and top edges on DWORD boundaries



    UINT WindowFlags = (SWP_NOACTIVATE | SWP_FRAMECHANGED);

    WindowRect.left -= (WindowRect.left & 3);

    WindowRect.top -= (WindowRect.top & 3);



    SetWindowPos(m_hwnd,                // Window handle

                 HWND_TOP,              // Put it at the top

                 WindowRect.left,       // Align left edge

                 WindowRect.top,        // And also top place

                 WIDTH(&ClientRect),    // Horizontal size

                 HEIGHT(&ClientRect),   // Vertical size

                 WindowFlags);          // Don't show window



    m_bActivated = TRUE;

    return NOERROR;

}





// This can be used to DWORD align the window for maximum performance



HRESULT CBaseWindow::PerformanceAlignWindow()

{

    RECT ClientRect,WindowRect;

    GetWindowRect(m_hwnd,&WindowRect);

    ASSERT(m_bActivated == TRUE);



    // Don't do this if we're owned



    if (GetParent(m_hwnd)) {

        return NOERROR;

    }



    // Align left and top edges on DWORD boundaries



    GetClientRect(m_hwnd, &ClientRect);

    MapWindowPoints(m_hwnd, HWND_DESKTOP, (LPPOINT) &ClientRect, 2);

    WindowRect.left -= (ClientRect.left & 3);

    WindowRect.top  -= (ClientRect.top  & 3);

    UINT WindowFlags = (SWP_NOACTIVATE | SWP_NOSIZE);



    SetWindowPos(m_hwnd,                // Window handle

                 HWND_TOP,              // Put it at the top

                 WindowRect.left,       // Align left edge

                 WindowRect.top,        // And also top place

                 (int) 0,(int) 0,       // Ignore these sizes

                 WindowFlags);          // Don't show window



    return NOERROR;

}





// Install a palette into the base window - we may be called by a different

// thread to the one that owns the window. We have to be careful how we do

// the palette realisation as we could be a different thread to the window

// which would cause an inter thread send message. Therefore we realise the

// palette by sending it a special message but without the window locked



HRESULT CBaseWindow::SetPalette(HPALETTE hPalette)

{

    // We must own the window lock during the change

    {

        CAutoLock cWindowLock(&m_WindowLock);

        CAutoLock cPaletteLock(&m_PaletteLock);

        ASSERT(hPalette);

        m_hPalette = hPalette;

    }

    return SetPalette();

}





HRESULT CBaseWindow::SetPalette()

{

    if (!m_bNoRealize) {

        SendMessage(m_hwnd, m_RealizePalette, 0, 0);

        return S_OK;

    } else {

        // Just select the palette

        ASSERT(m_hdc);

        ASSERT(m_MemoryDC);



        CAutoLock cPaletteLock(&m_PaletteLock);

        SelectPalette(m_hdc,m_hPalette,m_bBackground);

        SelectPalette(m_MemoryDC,m_hPalette,m_bBackground);



        return S_OK;

    }

}





void CBaseWindow::UnsetPalette()

{

    CAutoLock cWindowLock(&m_WindowLock);

    CAutoLock cPaletteLock(&m_PaletteLock);



    // Get a standard VGA colour palette



    HPALETTE hPalette = (HPALETTE) GetStockObject(DEFAULT_PALETTE);

    ASSERT(hPalette);



    SelectPalette(GetWindowHDC(), hPalette, TRUE);

    SelectPalette(GetMemoryHDC(), hPalette, TRUE);



    m_hPalette = NULL;

}





void CBaseWindow::LockPaletteLock()

{

    m_PaletteLock.Lock();

}





void CBaseWindow::UnlockPaletteLock()

{

    m_PaletteLock.Unlock();

}





// Realise our palettes in the window and device contexts



HRESULT CBaseWindow::DoRealisePalette(BOOL bForceBackground)

{

    {

        CAutoLock cPaletteLock(&m_PaletteLock);



        if (m_hPalette == NULL) {

            return NOERROR;

        }



        // Realize the palette on the window thread

        ASSERT(m_hdc);

        ASSERT(m_MemoryDC);



        SelectPalette(m_hdc,m_hPalette,m_bBackground || bForceBackground);

        SelectPalette(m_MemoryDC,m_hPalette,m_bBackground);

    }



    //  If we grab a critical section here we can deadlock

    //  with the window thread because one of the side effects

    //  of RealizePalette is to send a WM_PALETTECHANGED message

    //  to every window in the system.  In our handling

    //  of WM_PALETTECHANGED we used to grab this CS too.

    //  The really bad case is when our renderer calls DoRealisePalette()

    //  while we're in the middle of processing a palette change

    //  for another window.

    //  So don't hold the critical section while actually realising

    //  the palette.  In any case USER is meant to manage palette

    //  handling - we shouldn't have to serialize everything as well

    ASSERT(CritCheckOut(&m_WindowLock));

    ASSERT(CritCheckOut(&m_PaletteLock));



    EXECUTE_ASSERT(RealizePalette(m_hdc) != GDI_ERROR);

    EXECUTE_ASSERT(RealizePalette(m_MemoryDC) != GDI_ERROR);



    return (GdiFlush() == FALSE ? S_FALSE : S_OK);

}





// This is the global window procedure



LRESULT CALLBACK WndProc(HWND hwnd,         // Window handle

                         UINT uMsg,         // Message ID

                         WPARAM wParam,     // First parameter

                         LPARAM lParam)     // Other parameter

{



    // Get the window long that holds our window object pointer

    // If it is NULL then we are initialising the window in which

    // case the object pointer has been passed in the window creation

    // structure.  IF we get any messages before WM_NCCREATE we will

    // pass them to DefWindowProc.



    CBaseWindow *pBaseWindow = (CBaseWindow *)GetWindowLongPtr(hwnd,0);

    if (pBaseWindow == NULL) {



        // Get the structure pointer from the create struct.

        // We can only do this for WM_NCCREATE which should be one of

        // the first messages we receive.  Anything before this will

        // have to be passed to DefWindowProc (i.e. WM_GETMINMAXINFO)



        // If the message is WM_NCCREATE we set our pBaseWindow pointer

        // and will then place it in the window structure



        // turn off WS_EX_LAYOUTRTL style for quartz windows

        if (uMsg == WM_NCCREATE) {

            SetWindowLong(hwnd, GWL_EXSTYLE, GetWindowLong(hwnd, GWL_EXSTYLE) & ~0x400000);

        }



        if ((uMsg != WM_NCCREATE)

            || (NULL == (pBaseWindow = *(CBaseWindow**) ((LPCREATESTRUCT)lParam)->lpCreateParams)))

        {

            return(DefWindowProc(hwnd, uMsg, wParam, lParam));

        }



        // Set the window LONG to be the object who created us

#ifdef DEBUG

        SetLastError(0);  // because of the way SetWindowLong works

#endif

        LONG_PTR rc = SetWindowLongPtr(hwnd, (DWORD) 0, (LONG_PTR) pBaseWindow);

#ifdef DEBUG

        if (0 == rc) {

            // SetWindowLong MIGHT have failed.  (Read the docs which admit

            // that it is awkward to work out if you have had an error.)

            LONG lasterror = GetLastError();

            ASSERT(0 == lasterror);

            // If this is not the case we have not set the pBaseWindow pointer

            // into the window structure and we will blow up.

        }

#endif



    }

    // See if this is the packet of death

    if (uMsg == MsgDestroy && uMsg != 0) {

        pBaseWindow->DoneWithWindow();

        if (pBaseWindow->m_bDoPostToDestroy) {

            EXECUTE_ASSERT(SetEvent((HANDLE)wParam));

        }

        return 0;

    }

    return pBaseWindow->OnReceiveMessage(hwnd,uMsg,wParam,lParam);

}





// When the window size changes we adjust our member variables that

// contain the dimensions of the client rectangle for our window so

// that we come to render an image we will know whether to stretch



BOOL CBaseWindow::OnSize(LONG Width, LONG Height)

{

    m_Width = Width;

    m_Height = Height;

    return TRUE;

}





// This function handles the WM_CLOSE message



BOOL CBaseWindow::OnClose()

{

    ShowWindow(m_hwnd,SW_HIDE);

    return TRUE;

}





// This is called by the worker window thread when it receives a terminate

// message from the window object destructor to delete all the resources we

// allocated during initialisation. By the time the worker thread exits all

// processing will have been completed as the source filter disconnection

// flushes the image pending sample, therefore the GdiFlush should succeed



HRESULT CBaseWindow::UninitialiseWindow()

{

    // Have we already cleaned up



    if (m_hwnd == NULL) {

        ASSERT(m_hdc == NULL);

        ASSERT(m_MemoryDC == NULL);

        return NOERROR;

    }



    // Release the window resources



    EXECUTE_ASSERT(GdiFlush());



    if (m_hdc)

    {

        EXECUTE_ASSERT(ReleaseDC(m_hwnd,m_hdc));

        m_hdc = NULL;

    }



    if (m_MemoryDC)

    {

        EXECUTE_ASSERT(DeleteDC(m_MemoryDC));

        m_MemoryDC = NULL;

    }



    // Reset the window variables

    m_hwnd = NULL;



    return NOERROR;

}





// This is called by the worker window thread after it has created the main

// window and it wants to initialise the rest of the owner objects window

// variables such as the device contexts. We execute this function with the

// critical section still locked. Nothing in this function must generate any

// SendMessage calls to the window because this is executing on the window

// thread so the message will never be processed and we will deadlock



#pragma warning(disable:4706)  // C4706: assignment within conditional expression



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;

}



#pragma warning(default:4706)  // C4706: assignment within conditional expression





HRESULT CBaseWindow::DoCreateWindow()

{

    WNDCLASS wndclass;                  // Used to register classes

    BOOL bRegistered;                   // Is this class registered

    HWND hwnd;                          // Handle to our window



    bRegistered = GetClassInfo(m_hInstance,   // Module instance

                               m_pClassName,  // Window class

                               &wndclass);                 // Info structure



    // if the window is to be used for drawing puposes and we are getting a DC

    // for the entire lifetime of the window then changes the class style to do

    // say so. If we don't set this flag then the DC comes from the cache and is

    // really bad.

    if (m_bDoGetDC)

    {

        m_ClassStyles |= CS_OWNDC;

    }



    if (bRegistered == FALSE) {



        // Register the renderer window class



        wndclass.lpszClassName = m_pClassName;

        wndclass.style         = m_ClassStyles;

        wndclass.lpfnWndProc   = WndProc;

        wndclass.cbClsExtra    = 0;

        wndclass.cbWndExtra    = sizeof(CBaseWindow *);

        wndclass.hInstance     = m_hInstance;

        wndclass.hIcon         = NULL;

        wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW);

        wndclass.hbrBackground = (HBRUSH) NULL;

        wndclass.lpszMenuName  = NULL;



        RegisterClass(&wndclass);

    }



    // Create the frame window.  Pass the pBaseWindow information in the

    // CreateStruct which allows our message handling loop to get hold of

    // the pBaseWindow pointer.



    CBaseWindow *pBaseWindow = this;                      // The owner window object

    hwnd = CreateWindowEx(m_WindowStylesEx,               // Extended styles

                          m_pClassName,                   // Registered name

                          TEXT("ActiveMovie Window"),     // Window title

                          m_WindowStyles,                 // Window styles

                          CW_USEDEFAULT,                  // Start x position

                          CW_USEDEFAULT,                  // Start y position

                          DEFWIDTH,                       // Window width

                          DEFHEIGHT,                      // Window height

                          NULL,                           // Parent handle

                          NULL,                           // Menu handle

                          m_hInstance,                    // Instance handle

                          &pBaseWindow);                  // Creation data



    // If we failed signal an error to the object constructor (based on the

    // last Win32 error on this thread) then signal the constructor thread

    // to continue, release the mutex to let others have a go and exit



    if (hwnd == NULL) {

        DWORD Error = GetLastError();

        return AmHresultFromWin32(Error);

    }



    // Check the window LONG is the object who created us

    ASSERT(GetWindowLongPtr(hwnd, 0) == (LONG_PTR)this);



    // Initialise the window and then signal the constructor so that it can

    // continue and then finally unlock the object's critical section. The

    // window class is left registered even after we terminate the thread

    // as we don't know when the last window has been closed. So we allow

    // the operating system to free the class resources as appropriate



    InitialiseWindow(hwnd);



    DbgLog((LOG_TRACE, 2, TEXT("Created window class (%s) HWND(%8.8X)"),

            m_pClassName, hwnd));



    return S_OK;

}





// The base class provides some default handling and calls DefWindowProc



LRESULT CBaseWindow::OnReceiveMessage(HWND hwnd,         // Window handle

                                      UINT uMsg,         // Message ID

                                      WPARAM wParam,     // First parameter

                                      LPARAM lParam)     // Other parameter

{

    ASSERT(IsWindow(hwnd));



    if (PossiblyEatMessage(uMsg, wParam, lParam))

        return 0;



    // This is sent by the IVideoWindow SetWindowForeground method. If the

    // window is invisible we will show it and make it topmost without the

    // foreground focus. If the window is visible it will also be made the

    // topmost window without the foreground focus. If wParam is TRUE then

    // for both cases the window will be forced into the foreground focus



    if (uMsg == m_ShowStageMessage) {



        BOOL bVisible = IsWindowVisible(hwnd);

        SetWindowPos(hwnd, HWND_TOP, 0, 0, 0, 0,

                     SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW |

                     (bVisible ? SWP_NOACTIVATE : 0));



        // Should we bring the window to the foreground

        if (wParam == TRUE) {

            SetForegroundWindow(hwnd);

        }

        return (LRESULT) 1;

    }



    // When we go fullscreen we have to add the WS_EX_TOPMOST style to the

    // video window so that it comes out above any task bar (this is more

    // relevant to WindowsNT than Windows95). However the SetWindowPos call

    // must be on the same thread as that which created the window. The

    // wParam parameter can be TRUE or FALSE to set and reset the topmost



    if (uMsg == m_ShowStageTop) {

        HWND HwndTop = (wParam == TRUE ? HWND_TOPMOST : HWND_NOTOPMOST);

        BOOL bVisible = IsWindowVisible(hwnd);

        SetWindowPos(hwnd, HwndTop, 0, 0, 0, 0,

                     SWP_NOMOVE | SWP_NOSIZE |

                     (wParam == TRUE ? SWP_SHOWWINDOW : 0) |

                     (bVisible ? SWP_NOACTIVATE : 0));

        return (LRESULT) 1;

    }



    // New palette stuff

    if (uMsg == m_RealizePalette) {

        ASSERT(m_hwnd == hwnd);

        return OnPaletteChange(m_hwnd,WM_QUERYNEWPALETTE);

    }



    switch (uMsg) {



        // Repaint the window if the system colours change



    case WM_SYSCOLORCHANGE:



        InvalidateRect(hwnd,NULL,FALSE);

        return (LRESULT) 1;



    // Somebody has changed the palette

    case WM_PALETTECHANGED:



        OnPaletteChange((HWND)wParam,uMsg);

        return (LRESULT) 0;



        // We are about to receive the keyboard focus so we ask GDI to realise

        // our logical palette again and hopefully it will be fully installed

        // without any mapping having to be done during any picture rendering



    case WM_QUERYNEWPALETTE:

        ASSERT(m_hwnd == hwnd);

        return OnPaletteChange(m_hwnd,uMsg);



    // do NOT fwd WM_MOVE. the parameters are the location of the parent

    // window, NOT what the renderer should be looking at.  But we need

    // to make sure the overlay is moved with the parent window, so we

    // do this.

    case WM_MOVE:

        if (IsWindowVisible(m_hwnd)) {

            PostMessage(m_hwnd,WM_PAINT,0,0);

        }

        break;



    // Store the width and height as useful base class members



    case WM_SIZE:



        OnSize(LOWORD(lParam), HIWORD(lParam));

        return (LRESULT) 0;



    // Intercept the WM_CLOSE messages to hide the window



    case WM_CLOSE:



        OnClose();

        return (LRESULT) 0;

    }

    return DefWindowProc(hwnd,uMsg,wParam,lParam);

}





// This handles the Windows palette change messages - if we do realise our

// palette then we return TRUE otherwise we return FALSE. If our window is

// foreground application then we should get first choice of colours in the

// system palette entries. We get best performance when our logical palette

// includes the standard VGA colours (at the beginning and end) otherwise

// GDI may have to map from our palette to the device palette while drawing



LRESULT CBaseWindow::OnPaletteChange(HWND hwnd,UINT Message)

{

    // First check we are not changing the palette during closedown



    if (m_hwnd == NULL || hwnd == NULL) {

        return (LRESULT) 0;

    }

    ASSERT(!m_bRealizing);



    // Should we realise our palette again



    if ((Message == WM_QUERYNEWPALETTE || hwnd != m_hwnd)) {

        //  It seems that even if we're invisible that we can get asked

        //  to realize our palette and this can cause really ugly side-effects

        //  Seems like there's another bug but this masks it a least for the

        //  shutting down case.

        if (!IsWindowVisible(m_hwnd)) {

            DbgLog((LOG_TRACE, 1, TEXT("Realizing when invisible!")));

            return (LRESULT) 0;

        }



        // Avoid recursion with multiple graphs in the same app

#ifdef DEBUG

        m_bRealizing = TRUE;

#endif

        DoRealisePalette(Message != WM_QUERYNEWPALETTE);

#ifdef DEBUG

        m_bRealizing = FALSE;

#endif



        // Should we redraw the window with the new palette

        if (Message == WM_PALETTECHANGED) {

            InvalidateRect(m_hwnd,NULL,FALSE);

        }

    }



    return (LRESULT) 1;

}





// Determine if the window exists.



bool CBaseWindow::WindowExists()

{

    return !!IsWindow(m_hwnd);

}





// Return the default window rectangle



RECT CBaseWindow::GetDefaultRect()

{

    RECT DefaultRect = {0,0,DEFWIDTH,DEFHEIGHT};

    ASSERT(m_hwnd);

    // ASSERT(m_hdc);

    return DefaultRect;

}





// Return the current window width



LONG CBaseWindow::GetWindowWidth()

{

    ASSERT(m_hwnd);

    // ASSERT(m_hdc);

    return m_Width;

}





// Return the current window height



LONG CBaseWindow::GetWindowHeight()

{

    ASSERT(m_hwnd);

    // ASSERT(m_hdc);

    return m_Height;

}





// Return the window handle



HWND CBaseWindow::GetWindowHWND()

{

    ASSERT(m_hwnd);

    // ASSERT(m_hdc);

    return m_hwnd;

}





// Return the window drawing device context



HDC CBaseWindow::GetWindowHDC()

{

    ASSERT(m_hwnd);

    ASSERT(m_hdc);

    return m_hdc;

}





// Return the offscreen window drawing device context



HDC CBaseWindow::GetMemoryHDC()

{

    ASSERT(m_hwnd);

    ASSERT(m_MemoryDC);

    return m_MemoryDC;

}





#ifdef DEBUG

HPALETTE CBaseWindow::GetPalette()

{

    // The palette lock should always be held when accessing

    // m_hPalette.

    ASSERT(CritCheckIn(&m_PaletteLock));

    return m_hPalette;

}

#endif // DEBUG





// This is available to clients who want to change the window visiblity. It's

// little more than an indirection to the Win32 ShowWindow although these is

// some benefit in going through here as this function may change sometime



HRESULT CBaseWindow::DoShowWindow(LONG ShowCmd)

{

    ShowWindow(m_hwnd,ShowCmd);

    return NOERROR;

}





// Generate a WM_PAINT message for the video window



void CBaseWindow::PaintWindow(BOOL bErase)

{

    InvalidateRect(m_hwnd,NULL,bErase);

}





// Allow an application to have us set the video window in the foreground. We

// have this because it is difficult for one thread to do do this to a window

// owned by another thread. Rather than expose the message we use to execute

// the inter thread send message we provide the interface function. All we do

// is to SendMessage to the video window renderer thread with a WM_SHOWSTAGE



void CBaseWindow::DoSetWindowForeground(BOOL bFocus)

{

    SendMessage(m_hwnd,m_ShowStageMessage,(WPARAM) bFocus,(LPARAM) 0);

}





// Constructor initialises the owning object pointer. Since we are a worker

// class for the main window object we have relatively few state variables to

// look after. We are given device context handles to use later on as well as

// the source and destination rectangles (but reset them here just in case)



CDrawImage::CDrawImage(CBaseWindow *pBaseWindow) :

    m_pBaseWindow(pBaseWindow),

    m_hdc(NULL),

    m_MemoryDC(NULL),

    m_bStretch(FALSE),

    m_pMediaType(NULL),

    m_bUsingImageAllocator(FALSE)

{

    ASSERT(pBaseWindow);

    ResetPaletteVersion();

    SetRectEmpty(&m_TargetRect);

    SetRectEmpty(&m_SourceRect);



    m_perfidRenderTime = MSR_REGISTER(TEXT("Single Blt time"));

}





// Overlay the image time stamps on the picture. Access to this method is

// serialised by the caller. We display the sample start and end times on

// top of the video using TextOut on the device context we are handed. If

// there isn't enough room in the window for the times we don't show them



void CDrawImage::DisplaySampleTimes(IMediaSample *pSample)

{

#ifdef DEBUG

    //

    // Only allow the "annoying" time messages if the users has turned the

    // logging "way up"

    //

    BOOL bAccept = DbgCheckModuleLevel(LOG_TRACE, 5);

    if (bAccept == FALSE) {

        return;

    }

#endif



    TCHAR szTimes[TIMELENGTH];      // Time stamp strings

    ASSERT(pSample);                // Quick sanity check

    RECT ClientRect;                // Client window size

    SIZE Size;                      // Size of text output



    // Get the time stamps and window size



    pSample->GetTime((REFERENCE_TIME*)&m_StartSample, (REFERENCE_TIME*)&m_EndSample);

    HWND hwnd = m_pBaseWindow->GetWindowHWND();

    EXECUTE_ASSERT(GetClientRect(hwnd,&ClientRect));



    // Format the sample time stamps



    wsprintf(szTimes,TEXT("%08d : %08d"),

             m_StartSample.Millisecs(),

             m_EndSample.Millisecs());



    ASSERT(lstrlen(szTimes) < TIMELENGTH);



    // Put the times in the middle at the bottom of the window



    GetTextExtentPoint32(m_hdc,szTimes,lstrlen(szTimes),&Size);

    INT XPos = ((ClientRect.right - ClientRect.left) - Size.cx) / 2;

    INT YPos = ((ClientRect.bottom - ClientRect.top) - Size.cy) * 4 / 5;



    // Check the window is big enough to have sample times displayed



    if ((XPos > 0) && (YPos > 0)) {

        TextOut(m_hdc,XPos,YPos,szTimes,lstrlen(szTimes));

    }

}





// This is called when the drawing code sees that the image has a down level

// palette cookie. We simply call the SetDIBColorTable Windows API with the

// palette that is found after the BITMAPINFOHEADER - we return no errors



void CDrawImage::UpdateColourTable(HDC hdc,BITMAPINFOHEADER *pbmi)

{

    ASSERT(pbmi->biClrUsed);

    RGBQUAD *pColourTable = (RGBQUAD *)(pbmi+1);



    // Set the new palette in the device context



    UINT uiReturn = SetDIBColorTable(hdc,(UINT) 0,

                                     pbmi->biClrUsed,

                                     pColourTable);



    // Should always succeed but check in debug builds

    ASSERT(uiReturn == pbmi->biClrUsed);

}





// No source rectangle scaling is done by the base class



RECT CDrawImage::ScaleSourceRect(const RECT *pSource)

{

    ASSERT(pSource);

    return *pSource;

}





// This is called when the funky output pin uses our allocator. The samples we

// allocate are special because the memory is shared between us and GDI thus

// removing one copy when we ask for the image to be rendered. The source type

// information is in the main renderer m_mtIn field which is initialised when

// the media type is agreed in SetMediaType, the media type may be changed on

// the fly if, for example, the source filter needs to change the palette



void CDrawImage::FastRender(IMediaSample *pMediaSample)

{

    BITMAPINFOHEADER *pbmi;     // Image format data

    DIBDATA *pDibData;          // Stores DIB information

    BYTE *pImage;               // Pointer to image data

    HBITMAP hOldBitmap;         // Store the old bitmap

    CImageSample *pSample;      // Pointer to C++ object



    ASSERT(m_pMediaType);



    // From the untyped source format block get the VIDEOINFO and subsequently

    // the BITMAPINFOHEADER structure. We can cast the IMediaSample interface

    // to a CImageSample object so we can retrieve it's DIBSECTION details



    pbmi = HEADER(m_pMediaType->Format());

    pSample = (CImageSample *) pMediaSample;

    pDibData = pSample->GetDIBData();

    hOldBitmap = (HBITMAP) SelectObject(m_MemoryDC,pDibData->hBitmap);



    // Get a pointer to the real image data



    HRESULT hr = pMediaSample->GetPointer(&pImage);

    if (FAILED(hr)) {

        return;

    }



    // Do we need to update the colour table, we increment our palette cookie

    // each time we get a dynamic format change. The sample palette cookie is

    // stored in the DIBDATA structure so we try to keep the fields in sync

    // By the time we get to draw the images the format change will be done

    // so all we do is ask the renderer for what it's palette version is



    if (pDibData->PaletteVersion < GetPaletteVersion()) {

        ASSERT(pbmi->biBitCount <= iPALETTE);

        UpdateColourTable(m_MemoryDC,pbmi);

        pDibData->PaletteVersion = GetPaletteVersion();

    }



    // This allows derived classes to change the source rectangle that we do

    // the drawing with. For example a renderer may ask a codec to stretch

    // the video from 320x240 to 640x480, in which case the source we see in

    // here will still be 320x240, although the source we want to draw with

    // should be scaled up to 640x480. The base class implementation of this

    // method does nothing but return the same rectangle as we are passed in



    RECT SourceRect = ScaleSourceRect(&m_SourceRect);



    // Is the window the same size as the video



    if (m_bStretch == FALSE) {



        // Put the image straight into the window



        BitBlt(

            (HDC) m_hdc,                            // Target device HDC

            m_TargetRect.left,                      // X sink position

            m_TargetRect.top,                       // Y sink position

            m_TargetRect.right - m_TargetRect.left, // Destination width

            m_TargetRect.bottom - m_TargetRect.top, // Destination height

            m_MemoryDC,                             // Source device context

            SourceRect.left,                        // X source position

            SourceRect.top,                         // Y source position

            SRCCOPY);                               // Simple copy



    } else {



        // Stretch the image when copying to the window



        StretchBlt(

            (HDC) m_hdc,                            // Target device HDC

            m_TargetRect.left,                      // X sink position

            m_TargetRect.top,                       // Y sink position

            m_TargetRect.right - m_TargetRect.left, // Destination width

            m_TargetRect.bottom - m_TargetRect.top, // Destination height

            m_MemoryDC,                             // Source device HDC

            SourceRect.left,                        // X source position

            SourceRect.top,                         // Y source position

            SourceRect.right - SourceRect.left,     // Source width

            SourceRect.bottom - SourceRect.top,     // Source height

            SRCCOPY);                               // Simple copy

    }



    // This displays the sample times over the top of the image. This used to

    // draw the times into the offscreen device context however that actually

    // writes the text into the image data buffer which may not be writable



    #ifdef DEBUG

    DisplaySampleTimes(pMediaSample);

    #endif



    // Put the old bitmap back into the device context so we don't leak

    SelectObject(m_MemoryDC,hOldBitmap);

}





// This is called when there is a sample ready to be drawn, unfortunately the

// output pin was being rotten and didn't choose our super excellent shared

// memory DIB allocator so we have to do this slow render using boring old GDI

// SetDIBitsToDevice and StretchDIBits. The down side of using these GDI

// functions is that the image data has to be copied across from our address

// space into theirs before going to the screen (although in reality the cost

// is small because all they do is to map the buffer into their address space)



void CDrawImage::SlowRender(IMediaSample *pMediaSample)

{

    // Get the BITMAPINFOHEADER for the connection



    ASSERT(m_pMediaType);

    BITMAPINFOHEADER *pbmi = HEADER(m_pMediaType->Format());

    BYTE *pImage;



    // Get the image data buffer



    HRESULT hr = pMediaSample->GetPointer(&pImage);

    if (FAILED(hr)) {

        return;

    }



    // This allows derived classes to change the source rectangle that we do

    // the drawing with. For example a renderer may ask a codec to stretch

    // the video from 320x240 to 640x480, in which case the source we see in

    // here will still be 320x240, although the source we want to draw with

    // should be scaled up to 640x480. The base class implementation of this

    // method does nothing but return the same rectangle as we are passed in



    RECT SourceRect = ScaleSourceRect(&m_SourceRect);



    LONG lAdjustedSourceTop = SourceRect.top;

    // if the origin of bitmap is bottom-left, adjust soruce_rect_top

    // to be the bottom-left corner instead of the top-left.

    if (pbmi->biHeight > 0) {

       lAdjustedSourceTop = pbmi->biHeight - SourceRect.bottom;

    }

    // Is the window the same size as the video



    if (m_bStretch == FALSE) {



        // Put the image straight into the window



        SetDIBitsToDevice(

            (HDC) m_hdc,                            // Target device HDC

            m_TargetRect.left,                      // X sink position

            m_TargetRect.top,                       // Y sink position

            m_TargetRect.right - m_TargetRect.left, // Destination width

            m_TargetRect.bottom - m_TargetRect.top, // Destination height

            SourceRect.left,                        // X source position

            lAdjustedSourceTop,                     // Adjusted Y source position

            (UINT) 0,                               // Start scan line

            pbmi->biHeight,                         // Scan lines present

            pImage,                                 // Image data

            (BITMAPINFO *) pbmi,                    // DIB header

            DIB_RGB_COLORS);                        // Type of palette



    } else {



        // Stretch the image when copying to the window



        StretchDIBits(

            (HDC) m_hdc,                            // Target device HDC

            m_TargetRect.left,                      // X sink position

            m_TargetRect.top,                       // Y sink position

            m_TargetRect.right - m_TargetRect.left, // Destination width

            m_TargetRect.bottom - m_TargetRect.top, // Destination height

            SourceRect.left,                        // X source position

            lAdjustedSourceTop,                     // Adjusted Y source position

            SourceRect.right - SourceRect.left,     // Source width

            SourceRect.bottom - SourceRect.top,     // Source height

            pImage,                                 // Image data

            (BITMAPINFO *) pbmi,                    // DIB header

            DIB_RGB_COLORS,                         // Type of palette

            SRCCOPY);                               // Simple image copy

    }



    // This shows the sample reference times over the top of the image which

    // looks a little flickery. I tried using GdiSetBatchLimit and GdiFlush to

    // control the screen updates but it doesn't quite work as expected and

    // only partially reduces the flicker. I also tried using a memory context

    // and combining the two in that before doing a final BitBlt operation to

    // the screen, unfortunately this has considerable performance penalties

    // and also means that this code is not executed when compiled retail



    #ifdef DEBUG

    DisplaySampleTimes(pMediaSample);

    #endif

}





// This is called with an IMediaSample interface on the image to be drawn. We

// decide on the drawing mechanism based on who's allocator we are using. We

// may be called when the window wants an image painted by WM_PAINT messages

// We can't realise the palette here because we have the renderer lock, any

// call to realise may cause an interthread send message to the window thread

// which may in turn be waiting to get the renderer lock before servicing it



BOOL CDrawImage::DrawImage(IMediaSample *pMediaSample)

{

    ASSERT(m_hdc);

    ASSERT(m_MemoryDC);

    NotifyStartDraw();



    // If the output pin used our allocator then the samples passed are in

    // fact CVideoSample objects that contain CreateDIBSection data that we

    // use to do faster image rendering, they may optionally also contain a

    // DirectDraw surface pointer in which case we do not do the drawing



    if (m_bUsingImageAllocator == FALSE) {

        SlowRender(pMediaSample);

        EXECUTE_ASSERT(GdiFlush());

        NotifyEndDraw();

        return TRUE;

    }



    // This is a DIBSECTION buffer



    FastRender(pMediaSample);

    EXECUTE_ASSERT(GdiFlush());

    NotifyEndDraw();

    return TRUE;

}





BOOL CDrawImage::DrawVideoImageHere(

    HDC hdc,

    IMediaSample *pMediaSample,

    LPRECT lprcSrc,

    LPRECT lprcDst

    )

{

    ASSERT(m_pMediaType);

    BITMAPINFOHEADER *pbmi = HEADER(m_pMediaType->Format());

    BYTE *pImage;



    // Get the image data buffer



    HRESULT hr = pMediaSample->GetPointer(&pImage);

    if (FAILED(hr)) {

        return FALSE;

    }



    RECT SourceRect;

    RECT TargetRect;



    if (lprcSrc)

        SourceRect = *lprcSrc;

    else  

        SourceRect = ScaleSourceRect(&m_SourceRect);



    if (lprcDst)

        TargetRect = *lprcDst;

    else  

        TargetRect = m_TargetRect;



    LONG lAdjustedSourceTop = SourceRect.top;

    // if the origin of bitmap is bottom-left, adjust soruce_rect_top

    // to be the bottom-left corner instead of the top-left.

    if (pbmi->biHeight > 0) {

       lAdjustedSourceTop = pbmi->biHeight - SourceRect.bottom;

    }





    // Stretch the image when copying to the DC



    BOOL bRet = (0 != StretchDIBits(hdc,

                                    TargetRect.left,

                                    TargetRect.top,

                                    TargetRect.right - TargetRect.left,

                                    TargetRect.bottom - TargetRect.top,

                                    SourceRect.left,

                                    lAdjustedSourceTop,

                                    SourceRect.right - SourceRect.left,

                                    SourceRect.bottom - SourceRect.top,

                                    pImage,

                                    (BITMAPINFO *)pbmi,

                                    DIB_RGB_COLORS,

                                    SRCCOPY));

    return bRet;

}





// This is called by the owning window object after it has created the window

// and it's drawing contexts. We are constructed with the base window we'll

// be drawing into so when given the notification we retrive the device HDCs

// to draw with. We cannot call these in our constructor as they are virtual



void CDrawImage::SetDrawContext()

{

    m_MemoryDC = m_pBaseWindow->GetMemoryHDC();

    m_hdc = m_pBaseWindow->GetWindowHDC();

}





// This is called to set the target rectangle in the video window, it will be

// called whenever a WM_SIZE message is retrieved from the message queue. We

// simply store the rectangle and use it later when we do the drawing calls



void CDrawImage::SetTargetRect(RECT *pTargetRect)

{

    ASSERT(pTargetRect);

    m_TargetRect = *pTargetRect;

    SetStretchMode();

}





// Return the current target rectangle



void CDrawImage::GetTargetRect(RECT *pTargetRect)

{

    ASSERT(pTargetRect);

    *pTargetRect = m_TargetRect;

}





// This is called when we want to change the section of the image to draw. We

// use this information in the drawing operation calls later on. We must also

// see if the source and destination rectangles have the same dimensions. If

// not we must stretch during the drawing rather than a direct pixel copy



void CDrawImage::SetSourceRect(RECT *pSourceRect)

{

    ASSERT(pSourceRect);

    m_SourceRect = *pSourceRect;

    SetStretchMode();

}





// Return the current source rectangle



void CDrawImage::GetSourceRect(RECT *pSourceRect)

{

    ASSERT(pSourceRect);

    *pSourceRect = m_SourceRect;

}





// This is called when either the source or destination rectanges change so we

// can update the stretch flag. If the rectangles don't match we stretch the

// video during the drawing otherwise we call the fast pixel copy functions

// NOTE the source and/or the destination rectangle may be completely empty



void CDrawImage::SetStretchMode()

{

    // Calculate the overall rectangle dimensions



    LONG SourceWidth = m_SourceRect.right - m_SourceRect.left;

    LONG SinkWidth = m_TargetRect.right - m_TargetRect.left;

    LONG SourceHeight = m_SourceRect.bottom - m_SourceRect.top;

    LONG SinkHeight = m_TargetRect.bottom - m_TargetRect.top;



    m_bStretch = TRUE;

    if (SourceWidth == SinkWidth) {

        if (SourceHeight == SinkHeight) {

            m_bStretch = FALSE;

        }

    }

}





// Tell us whose allocator we are using. This should be called with TRUE if

// the filter agrees to use an allocator based around the CImageAllocator

// SDK base class - whose image buffers are made through CreateDIBSection.

// Otherwise this should be called with FALSE and we will draw the images

// using SetDIBitsToDevice and StretchDIBitsToDevice. None of these calls

// can handle buffers which have non zero strides (like DirectDraw uses)



void CDrawImage::NotifyAllocator(BOOL bUsingImageAllocator)

{

    m_bUsingImageAllocator = bUsingImageAllocator;

}





// Are we using the image DIBSECTION allocator



BOOL CDrawImage::UsingImageAllocator()

{

    return m_bUsingImageAllocator;

}





// We need the media type of the connection so that we can get the BITMAPINFO

// from it. We use that in the calls to draw the image such as StretchDIBits

// and also when updating the colour table held in shared memory DIBSECTIONs



void CDrawImage::NotifyMediaType(CMediaType *pMediaType)

{

    m_pMediaType = pMediaType;

}





// We store in this object a cookie maintaining the current palette version.

// Each time a palettised format is changed we increment this value so that

// when we come to draw the images we look at the colour table value they

// have and if less than the current we know to update it. This version is

// only needed and indeed used when working with shared memory DIBSECTIONs



LONG CDrawImage::GetPaletteVersion()

{

    return m_PaletteVersion;

}





// Resets the current palette version number



void CDrawImage::ResetPaletteVersion()

{

    m_PaletteVersion = PALETTE_VERSION;

}





// Increment the current palette version



void CDrawImage::IncrementPaletteVersion()

{

    m_PaletteVersion++;

}





// Constructor must initialise the base allocator. Each sample we create has a

// palette version cookie on board. When the source filter changes the palette

// during streaming the window object increments an internal cookie counter it

// keeps as well. When it comes to render the samples it looks at the cookie

// values and if they don't match then it knows to update the sample's colour

// table. However we always create samples with a cookie of PALETTE_VERSION

// If there have been multiple format changes and we disconnect and reconnect

// thereby causing the samples to be reallocated we will create them with a

// cookie much lower than the current version, this isn't a problem since it

// will be seen by the window object and the versions will then be updated



CImageAllocator::CImageAllocator(CBaseFilter *pFilter,

                                 TCHAR *pName,

                                 HRESULT *phr) :

    CBaseAllocator(pName,NULL,phr,TRUE,TRUE),

    m_pFilter(pFilter)

{

    ASSERT(phr);

    ASSERT(pFilter);

}





// Check our DIB buffers have been released



#ifdef DEBUG

CImageAllocator::~CImageAllocator()

{

    ASSERT(m_bCommitted == FALSE);

}

#endif





// Called from destructor and also from base class to free resources. We work

// our way through the list of media samples deleting the DIBSECTION created

// for each. All samples should be back in our list so there is no chance a

// filter is still using one to write on the display or hold on a pending list



void CImageAllocator::Free()

{

    ASSERT(m_lAllocated == m_lFree.GetCount());

    EXECUTE_ASSERT(GdiFlush());

    CImageSample *pSample;

    DIBDATA *pDibData;



    while (m_lFree.GetCount() != 0) {

        pSample = (CImageSample *) m_lFree.RemoveHead();

        pDibData = pSample->GetDIBData();

        EXECUTE_ASSERT(DeleteObject(pDibData->hBitmap));

        EXECUTE_ASSERT(CloseHandle(pDibData->hMapping));

        delete pSample;

    }



    m_lAllocated = 0;

}





// Prepare the allocator by checking all the input parameters



STDMETHODIMP CImageAllocator::CheckSizes(ALLOCATOR_PROPERTIES *pRequest)

{

    // Check we have a valid connection



    if (m_pMediaType == NULL) {

        return VFW_E_NOT_CONNECTED;

    }



    // NOTE We always create a DIB section with the source format type which

    // may contain a source palette. When we do the BitBlt drawing operation

    // the target display device may contain a different palette (we may not

    // have the focus) in which case GDI will do after the palette mapping



    VIDEOINFOHEADER *pVideoInfo = (VIDEOINFOHEADER *) m_pMediaType->Format();



    // When we call CreateDIBSection it implicitly maps only enough memory

    // for the image as defined by thee BITMAPINFOHEADER. If the user asks

    // for an image smaller than this then we reject the call, if they ask

    // for an image larger than this then we return what they can have



    if ((DWORD) pRequest->cbBuffer < pVideoInfo->bmiHeader.biSizeImage) {

        return E_INVALIDARG;

    }



    // Reject buffer prefixes



    if (pRequest->cbPrefix > 0) {

        return E_INVALIDARG;

    }



    pRequest->cbBuffer = pVideoInfo->bmiHeader.biSizeImage;

    return NOERROR;

}





// Agree the number of media sample buffers and their sizes. The base class

// this allocator is derived from allows samples to be aligned only on byte

// boundaries NOTE the buffers are not allocated until the Commit call



STDMETHODIMP CImageAllocator::SetProperties(

    ALLOCATOR_PROPERTIES * pRequest,

    ALLOCATOR_PROPERTIES * pActual)

{

    ALLOCATOR_PROPERTIES Adjusted = *pRequest;



    // Check the parameters fit with the current connection



    HRESULT hr = CheckSizes(&Adjusted);

    if (FAILED(hr)) {

        return hr;

    }

    return CBaseAllocator::SetProperties(&Adjusted, pActual);

}





// Commit the memory by allocating the agreed number of media samples. For

// each sample we are committed to creating we have a CImageSample object

// that we use to manage it's resources. This is initialised with a DIBDATA

// structure that contains amongst other things the GDI DIBSECTION handle

// We will access the renderer media type during this so we must have locked

// (to prevent the format changing for example). The class overrides Commit

// and Decommit to do this locking (base class Commit in turn calls Alloc)



HRESULT CImageAllocator::Alloc(void)

{

    ASSERT(m_pMediaType);

    CImageSample *pSample;

    DIBDATA DibData;



    // Check the base allocator says it's ok to continue



    HRESULT hr = CBaseAllocator::Alloc();

    if (FAILED(hr)) {

        return hr;

    }



    // We create a new memory mapped object although we don't map it into our

    // address space because GDI does that in CreateDIBSection. It is possible

    // that we run out of resources before creating all the samples in which

    // case the available sample list is left with those already created



    ASSERT(m_lAllocated == 0);

    while (m_lAllocated < m_lCount) {



        // Create and initialise a shared memory GDI buffer



        HRESULT hr = CreateDIB(m_lSize,DibData);

        if (FAILED(hr)) {

            return hr;

        }



        // Create the sample object and pass it the DIBDATA



        pSample = CreateImageSample(DibData.pBase,m_lSize);

        if (pSample == NULL) {

            EXECUTE_ASSERT(DeleteObject(DibData.hBitmap));

            EXECUTE_ASSERT(CloseHandle(DibData.hMapping));

            return E_OUTOFMEMORY;

        }



        // Add the completed sample to the available list



        pSample->SetDIBData(&DibData);

        m_lFree.Add(pSample);

        m_lAllocated++;

    }

    return NOERROR;

}





// We have a virtual method that allocates the samples so that a derived class

// may override it and allocate more specialised sample objects. So long as it

// derives its samples from CImageSample then all this code will still work ok



CImageSample *CImageAllocator::CreateImageSample(LPBYTE pData,LONG Length)

{

    HRESULT hr = NOERROR;

    CImageSample *pSample;



    // Allocate the new sample and check the return codes



    pSample = new CImageSample((CBaseAllocator *) this,   // Base class

                               NAME("Video sample"),      // DEBUG name

                               (HRESULT *) &hr,           // Return code

                               (LPBYTE) pData,            // DIB address

                               (LONG) Length);            // Size of DIB



    if (pSample == NULL || FAILED(hr)) {

        delete pSample;

        return NULL;

    }

    return pSample;

}





// This function allocates a shared memory block for use by the source filter

// generating DIBs for us to render. The memory block is created in shared

// memory so that GDI doesn't have to copy the memory when we do a BitBlt



HRESULT CImageAllocator::CreateDIB(LONG InSize,DIBDATA &DibData)

{

    BITMAPINFO *pbmi;       // Format information for pin

    BYTE *pBase;            // Pointer to the actual image

    HANDLE hMapping;        // Handle to mapped object

    HBITMAP hBitmap;        // DIB section bitmap handle



    // Create a file mapping object and map into our address space



    hMapping = CreateFileMapping(hMEMORY,         // Use system page file

                                 NULL,            // No security attributes

                                 PAGE_READWRITE,  // Full access to memory

                                 (DWORD) 0,       // Less than 4Gb in size

                                 InSize,          // Size of buffer

                                 NULL);           // No name to section

    if (hMapping == NULL) {

        DWORD Error = GetLastError();

        return MAKE_HRESULT(SEVERITY_ERROR, FACILITY_WIN32, Error);

    }



    // NOTE We always create a DIB section with the source format type which

    // may contain a source palette. When we do the BitBlt drawing operation

    // the target display device may contain a different palette (we may not

    // have the focus) in which case GDI will do after the palette mapping



    pbmi = (BITMAPINFO *) HEADER(m_pMediaType->Format());

    if (m_pMediaType == NULL) {

        DbgBreak("Invalid media type");

    }



    hBitmap = CreateDIBSection((HDC) NULL,          // NO device context

                               pbmi,                // Format information

                               DIB_RGB_COLORS,      // Use the palette

                               (VOID **) &pBase,    // Pointer to image data

                               hMapping,            // Mapped memory handle

                               (DWORD) 0);          // Offset into memory



    if (hBitmap == NULL || pBase == NULL) {

        EXECUTE_ASSERT(CloseHandle(hMapping));

        DWORD Error = GetLastError();

        return MAKE_HRESULT(SEVERITY_ERROR, FACILITY_WIN32, Error);

    }



    // Initialise the DIB information structure



    DibData.hBitmap = hBitmap;

    DibData.hMapping = hMapping;

    DibData.pBase = pBase;

    DibData.PaletteVersion = PALETTE_VERSION;

    GetObject(hBitmap,sizeof(DIBSECTION),(VOID *)&DibData.DibSection);



    return NOERROR;

}





// We use the media type during the DIBSECTION creation



void CImageAllocator::NotifyMediaType(CMediaType *pMediaType)

{

    m_pMediaType = pMediaType;

}





// Overriden to increment the owning object's reference count



STDMETHODIMP_(ULONG) CImageAllocator::NonDelegatingAddRef()

{

    return m_pFilter->AddRef();

}





// Overriden to decrement the owning object's reference count



STDMETHODIMP_(ULONG) CImageAllocator::NonDelegatingRelease()

{

    return m_pFilter->Release();

}





// If you derive a class from CMediaSample that has to transport specialised

// member variables and entry points then there are three alternate solutions

// The first is to create a memory buffer larger than actually required by the

// sample and store your information either at the beginning of it or at the

// end, the former being moderately safer allowing for misbehaving transform

// filters. You then adjust the buffer address when you create the base media

// sample. This has the disadvantage of breaking up the memory allocated to

// the samples into separate blocks. The second solution is to implement a

// class derived from CMediaSample and support additional interface(s) that

// convey your private data. This means defining a custom interface. The final

// alternative is to create a class that inherits from CMediaSample and adds

// the private data structures, when you get an IMediaSample in your Receive()

// call check to see if your allocator is being used, and if it is then cast

// the IMediaSample into one of your objects. Additional checks can be made

// to ensure the sample's this pointer is known to be one of your own objects



CImageSample::CImageSample(CBaseAllocator *pAllocator,

                           TCHAR *pName,

                           HRESULT *phr,

                           LPBYTE pBuffer,

                           LONG length) :

    CMediaSample(pName,pAllocator,phr,pBuffer,length),

    m_bInit(FALSE)

{

    ASSERT(pAllocator);

    ASSERT(pBuffer);

}





// Set the shared memory DIB information



void CImageSample::SetDIBData(DIBDATA *pDibData)

{

    ASSERT(pDibData);

    m_DibData = *pDibData;

    m_bInit = TRUE;

}





// Retrieve the shared memory DIB data



DIBDATA *CImageSample::GetDIBData()

{

    ASSERT(m_bInit == TRUE);

    return &m_DibData;

}





// This class handles the creation of a palette. It is fairly specialist and

// is intended to simplify palette management for video renderer filters. It

// is for this reason that the constructor requires three other objects with

// which it interacts, namely a base media filter, a base window and a base

// drawing object although the base window or the draw object may be NULL to

// ignore that part of us. We try not to create and install palettes unless

// absolutely necessary as they typically require WM_PALETTECHANGED messages

// to be sent to every window thread in the system which is very expensive



CImagePalette::CImagePalette(CBaseFilter *pBaseFilter,

                             CBaseWindow *pBaseWindow,

                             CDrawImage *pDrawImage) :

    m_pBaseWindow(pBaseWindow),

    m_pFilter(pBaseFilter),

    m_pDrawImage(pDrawImage),

    m_hPalette(NULL)

{

    ASSERT(m_pFilter);

}





// Destructor



#ifdef DEBUG

CImagePalette::~CImagePalette()

{

    ASSERT(m_hPalette == NULL);

}

#endif





// We allow dynamic format changes of the palette but rather than change the

// palette every time we call this to work out whether an update is required.

// If the original type didn't use a palette and the new one does (or vica

// versa) then we return TRUE. If neither formats use a palette we'll return

// FALSE. If both formats use a palette we compare their colours and return

// FALSE if they match. This therefore short circuits palette creation unless

// absolutely necessary since installing palettes is an expensive operation



BOOL CImagePalette::ShouldUpdate(const VIDEOINFOHEADER *pNewInfo,

                                 const VIDEOINFOHEADER *pOldInfo)

{

    // We may not have a current format yet



    if (pOldInfo == NULL) {

        return TRUE;

    }



    // Do both formats not require a palette



    if (ContainsPalette(pNewInfo) == FALSE) {

        if (ContainsPalette(pOldInfo) == FALSE) {

            return FALSE;

        }

    }



    // Compare the colours to see if they match



    DWORD VideoEntries = pNewInfo->bmiHeader.biClrUsed;

    if (ContainsPalette(pNewInfo) == TRUE)

        if (ContainsPalette(pOldInfo) == TRUE)

            if (pOldInfo->bmiHeader.biClrUsed == VideoEntries)

                if (pOldInfo->bmiHeader.biClrUsed > 0)

                    if (memcmp((PVOID) GetBitmapPalette(pNewInfo),

                               (PVOID) GetBitmapPalette(pOldInfo),

                               VideoEntries * sizeof(RGBQUAD)) == 0) {



                        return FALSE;

                    }

    return TRUE;

}





// This is normally called when the input pin type is set to install a palette

// We will typically be called from two different places. The first is when we

// have negotiated a palettised media type after connection, the other is when

// we receive a new type during processing with an updated palette in which

// case we must remove and release the resources held by the current palette



// We can be passed an optional device name if we wish to prepare a palette

// for a specific monitor on a multi monitor system



HRESULT CImagePalette::PreparePalette(const CMediaType *pmtNew,

                                      const CMediaType *pmtOld,

                      LPSTR szDevice)

{

    const VIDEOINFOHEADER *pNewInfo = (VIDEOINFOHEADER *) pmtNew->Format();

    const VIDEOINFOHEADER *pOldInfo = (VIDEOINFOHEADER *) pmtOld->Format();

    ASSERT(pNewInfo);



    // This is an performance optimisation, when we get a media type we check

    // to see if the format requires a palette change. If either we need one

    // when previously we didn't or vica versa then this returns TRUE, if we

    // previously needed a palette and we do now it compares their colours



    if (ShouldUpdate(pNewInfo,pOldInfo) == FALSE) {

        NOTE("No update needed");

        return S_FALSE;

    }



    // We must notify the filter graph that the application may have changed

    // the palette although in practice we don't bother checking to see if it

    // is really different. If it tries to get the palette either the window

    // or renderer lock will ensure it doesn't get in until we are finished



    RemovePalette();

    m_pFilter->NotifyEvent(EC_PALETTE_CHANGED,0,0);



    // Do we need a palette for the new format



    if (ContainsPalette(pNewInfo) == FALSE) {

        NOTE("New has no palette");

        return S_FALSE;

    }



    if (m_pBaseWindow) {

        m_pBaseWindow->LockPaletteLock();

    }



    // If we're changing the palette on the fly then we increment our palette

    // cookie which is compared against the cookie also stored in all of our

    // DIBSECTION media samples. If they don't match when we come to draw it

    // then we know the sample is out of date and we'll update it's palette



    NOTE("Making new colour palette");

    m_hPalette = MakePalette(pNewInfo, szDevice);

    ASSERT(m_hPalette != NULL);



    if (m_pBaseWindow) {

        m_pBaseWindow->UnlockPaletteLock();

    }



    // The window in which the new palette is to be realised may be a NULL

    // pointer to signal that no window is in use, if so we don't call it

    // Some filters just want to use this object to create/manage palettes



    if (m_pBaseWindow) m_pBaseWindow->SetPalette(m_hPalette);



    // This is the only time where we need access to the draw object to say

    // to it that a new palette will be arriving on a sample real soon. The

    // constructor may take a NULL pointer in which case we don't call this



    if (m_pDrawImage) m_pDrawImage->IncrementPaletteVersion();

    return NOERROR;

}





// Helper function to copy a palette out of any kind of VIDEOINFO (ie it may

// be YUV or true colour) into a palettised VIDEOINFO. We use this changing

// palettes on DirectDraw samples as a source filter can attach a palette to

// any buffer (eg YUV) and hand it back. We make a new palette out of that

// format and then copy the palette colours into the current connection type



HRESULT CImagePalette::CopyPalette(const CMediaType *pSrc,CMediaType *pDest)

{

    // Reset the destination palette before starting



    VIDEOINFOHEADER *pDestInfo = (VIDEOINFOHEADER *) pDest->Format();

    pDestInfo->bmiHeader.biClrUsed = 0;

    pDestInfo->bmiHeader.biClrImportant = 0;



    // Does the destination have a palette



    if (PALETTISED(pDestInfo) == FALSE) {

        NOTE("No destination palette");

        return S_FALSE;

    }



    // Does the source contain a palette



    const VIDEOINFOHEADER *pSrcInfo = (VIDEOINFOHEADER *) pSrc->Format();

    if (ContainsPalette(pSrcInfo) == FALSE) {

        NOTE("No source palette");

        return S_FALSE;

    }



    // The number of colours may be zero filled



    DWORD PaletteEntries = pSrcInfo->bmiHeader.biClrUsed;

    if (PaletteEntries == 0) {

        DWORD Maximum  = (1 << pSrcInfo->bmiHeader.biBitCount);

        NOTE1("Setting maximum colours (%d)",Maximum);

        PaletteEntries = Maximum;

    }



    // Make sure the destination has enough room for the palette



    ASSERT(pSrcInfo->bmiHeader.biClrUsed <= iPALETTE_COLORS);

    ASSERT(pSrcInfo->bmiHeader.biClrImportant <= PaletteEntries);

    ASSERT(COLORS(pDestInfo) == GetBitmapPalette(pDestInfo));

    pDestInfo->bmiHeader.biClrUsed = PaletteEntries;

    pDestInfo->bmiHeader.biClrImportant = pSrcInfo->bmiHeader.biClrImportant;

    ULONG BitmapSize = GetBitmapFormatSize(HEADER(pSrcInfo));



    if (pDest->FormatLength() < BitmapSize) {

        NOTE("Reallocating destination");

        pDest->ReallocFormatBuffer(BitmapSize);

    }



    // Now copy the palette colours across



    CopyMemory((PVOID) COLORS(pDestInfo),

               (PVOID) GetBitmapPalette(pSrcInfo),

               PaletteEntries * sizeof(RGBQUAD));



    return NOERROR;

}





// This is normally called when the palette is changed (typically during a

// dynamic format change) to remove any palette we previously installed. We

// replace it (if necessary) in the video window with a standard VGA palette

// that should always be available even if this is a true colour display



HRESULT CImagePalette::RemovePalette()

{

    if (m_pBaseWindow) {

        m_pBaseWindow->LockPaletteLock();

    }



    // Do we have a palette to remove



    if (m_hPalette != NULL) {



        if (m_pBaseWindow) {

            // Make sure that the window's palette handle matches

            // our palette handle.

            ASSERT(m_hPalette == m_pBaseWindow->GetPalette());



            m_pBaseWindow->UnsetPalette();

        }



        EXECUTE_ASSERT(DeleteObject(m_hPalette));

        m_hPalette = NULL;

    }



    if (m_pBaseWindow) {

        m_pBaseWindow->UnlockPaletteLock();

    }



    return NOERROR;

}





// Called to create a palette for the object, the data structure used by GDI

// to describe a palette is a LOGPALETTE, this includes a variable number of

// PALETTEENTRY fields which are the colours, we have to convert the RGBQUAD

// colour fields we are handed in a BITMAPINFO from the media type into these

// This handles extraction of palettes from true colour and YUV media formats



// We can be passed an optional device name if we wish to prepare a palette

// for a specific monitor on a multi monitor system



HPALETTE CImagePalette::MakePalette(const VIDEOINFOHEADER *pVideoInfo, LPSTR szDevice)

{

    ASSERT(ContainsPalette(pVideoInfo) == TRUE);

    ASSERT(pVideoInfo->bmiHeader.biClrUsed <= iPALETTE_COLORS);

    BITMAPINFOHEADER *pHeader = HEADER(pVideoInfo);



    const RGBQUAD *pColours;            // Pointer to the palette

    LOGPALETTE *lp;                     // Used to create a palette

    HPALETTE hPalette;                  // Logical palette object



    lp = (LOGPALETTE *) new BYTE[sizeof(LOGPALETTE) + SIZE_PALETTE];

    if (lp == NULL) {

        return NULL;

    }



    // Unfortunately for some hare brained reason a GDI palette entry (a

    // PALETTEENTRY structure) is different to a palette entry from a DIB

    // format (a RGBQUAD structure) so we have to do the field conversion

    // The VIDEOINFO containing the palette may be a true colour type so

    // we use GetBitmapPalette to skip over any bit fields if they exist



    lp->palVersion = PALVERSION;

    lp->palNumEntries = (USHORT) pHeader->biClrUsed;

    if (lp->palNumEntries == 0) 

        lp->palNumEntries = (WORD) (1 << pHeader->biBitCount);

    pColours = GetBitmapPalette(pVideoInfo);



    for (DWORD dwCount = 0;dwCount < lp->palNumEntries;dwCount++) {

        lp->palPalEntry[dwCount].peRed = pColours[dwCount].rgbRed;

        lp->palPalEntry[dwCount].peGreen = pColours[dwCount].rgbGreen;

        lp->palPalEntry[dwCount].peBlue = pColours[dwCount].rgbBlue;

        lp->palPalEntry[dwCount].peFlags = 0;

    }



    MakeIdentityPalette(lp->palPalEntry, lp->palNumEntries, szDevice);



    // Create a logical palette



    hPalette = CreatePalette(lp);

    ASSERT(hPalette != NULL);

    delete[] lp;

    return hPalette;

}





// GDI does a fair job of compressing the palette entries you give it, so for

// example if you have five entries with an RGB colour (0,0,0) it will remove

// all but one of them. When you subsequently draw an image it will map from

// your logical palette to the compressed device palette. This function looks

// to see if it is trying to be an identity palette and if so sets the flags

// field in the PALETTEENTRYs so they remain expanded to boost performance



// We can be passed an optional device name if we wish to prepare a palette

// for a specific monitor on a multi monitor system



HRESULT CImagePalette::MakeIdentityPalette(PALETTEENTRY *pEntry,INT iColours, LPSTR szDevice)

{

    PALETTEENTRY SystemEntries[10];         // System palette entries

    BOOL bIdentityPalette = TRUE;           // Is an identity palette

    ASSERT(iColours <= iPALETTE_COLORS);    // Should have a palette

    const int PalLoCount = 10;              // First ten reserved colours

    const int PalHiStart = 246;             // Last VGA palette entries



    // Does this have the full colour range



    if (iColours < 10) {

        return S_FALSE;

    }



    // Apparently some displays have odd numbers of system colours



    // Get a DC on the right monitor - it's ugly, but this is the way you have

    // to do it

    HDC hdc;

    if (szDevice == NULL || lstrcmpiA(szDevice, "DISPLAY") == 0)

        hdc = CreateDCA("DISPLAY", NULL, NULL, NULL);

    else

        hdc = CreateDCA(NULL, szDevice, NULL, NULL);

    if (NULL == hdc) {

        return E_OUTOFMEMORY;

    }

    INT Reserved = GetDeviceCaps(hdc,NUMRESERVED);

    if (Reserved != 20) {

        DeleteDC(hdc);

        return S_FALSE;

    }



    // Compare our palette against the first ten system entries. The reason I

    // don't do a memory compare between our two arrays of colours is because

    // I am not sure what will be in the flags fields for the system entries



    UINT Result = GetSystemPaletteEntries(hdc,0,PalLoCount,SystemEntries);

    for (UINT Count = 0;Count < Result;Count++) {

        if (SystemEntries[Count].peRed != pEntry[Count].peRed ||

                SystemEntries[Count].peGreen != pEntry[Count].peGreen ||

                    SystemEntries[Count].peBlue != pEntry[Count].peBlue) {

                        bIdentityPalette = FALSE;

        }

    }



    // And likewise compare against the last ten entries



    Result = GetSystemPaletteEntries(hdc,PalHiStart,PalLoCount,SystemEntries);

    for (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 (Count = PalLoCount;INT(Count) < min(PalHiStart,iColours);Count++) {

        pEntry[Count].peFlags = PC_NOCOLLAPSE;

    }

    return NOERROR;

}





// Constructor initialises the VIDEOINFO we keep storing the current display

// format. The format can be changed at any time, to reset the format held

// by us call the RefreshDisplayType directly (it's a public method). Since

// more than one thread will typically call us (ie window threads resetting

// the type and source threads in the type checking methods) we have a lock



CImageDisplay::CImageDisplay()

{

    RefreshDisplayType(NULL);

}







// This initialises the format we hold which contains the display device type

// We do a conversion on the display device type in here so that when we start

// type checking input formats we can assume that certain fields have been set

// correctly, an example is when we make the 16 bit mask fields explicit. This

// is normally called when we receive WM_DEVMODECHANGED device change messages



// The optional szDeviceName parameter tells us which monitor we are interested

// in for a multi monitor system



HRESULT CImageDisplay::RefreshDisplayType(LPSTR szDeviceName)

{

    CAutoLock cDisplayLock(this);



    // Set the preferred format type



    ZeroMemory((PVOID)&m_Display,sizeof(VIDEOINFOHEADER)+sizeof(TRUECOLORINFO));

    m_Display.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);

    m_Display.bmiHeader.biBitCount = FALSE;



    // Get the bit depth of a device compatible bitmap



    // get caps of whichever monitor they are interested in (multi monitor)

    HDC hdcDisplay;

    // it's ugly, but this is the way you have to do it

    if (szDeviceName == NULL || lstrcmpiA(szDeviceName, "DISPLAY") == 0)

        hdcDisplay = CreateDCA("DISPLAY", NULL, NULL, NULL);

    else

        hdcDisplay = CreateDCA(NULL, szDeviceName, NULL, NULL);

    if (hdcDisplay == NULL) {

    ASSERT(FALSE);

    DbgLog((LOG_ERROR,1,TEXT("ACK! Can't get a DC for %hs"),

                szDeviceName ? szDeviceName : "<NULL>"));

    return E_FAIL;

    } else {

    DbgLog((LOG_TRACE,3,TEXT("Created a DC for %s"),

                szDeviceName ? szDeviceName : "<NULL>"));

    }

    HBITMAP hbm = CreateCompatibleBitmap(hdcDisplay,1,1);

    if ( hbm )

    {

        GetDIBits(hdcDisplay,hbm,0,1,NULL,(BITMAPINFO *)&m_Display.bmiHeader,DIB_RGB_COLORS);



        // This call will get the colour table or the proper bitfields

        GetDIBits(hdcDisplay,hbm,0,1,NULL,(BITMAPINFO *)&m_Display.bmiHeader,DIB_RGB_COLORS);

        DeleteObject(hbm);

    }

    DeleteDC(hdcDisplay);



    // Complete the display type initialisation



    ASSERT(CheckHeaderValidity(&m_Display));

    UpdateFormat(&m_Display);

    DbgLog((LOG_TRACE,3,TEXT("New DISPLAY bit depth =%d"),

                m_Display.bmiHeader.biBitCount));

    return NOERROR;

}





// We assume throughout this code that any bitfields masks are allowed no

// more than eight bits to store a colour component. This checks that the

// bit count assumption is enforced and also makes sure that all the bits

// set are contiguous. We return a boolean TRUE if the field checks out ok



BOOL CImageDisplay::CheckBitFields(const VIDEOINFO *pInput)

{

    DWORD *pBitFields = (DWORD *) BITMASKS(pInput);



    for (INT iColour = iRED;iColour <= iBLUE;iColour++) {



        // First of all work out how many bits are set



        DWORD SetBits = CountSetBits(pBitFields[iColour]);

        if (SetBits > iMAXBITS || SetBits == 0) {

            NOTE1("Bit fields for component %d invalid",iColour);

            return FALSE;

        }



        // Next work out the number of zero bits prefix

        DWORD PrefixBits = CountPrefixBits(pBitFields[iColour]);



        // This is going to see if all the bits set are contiguous (as they

        // should be). We know how much to shift them right by from the

        // count of prefix bits. The number of bits set defines a mask, we

        // invert this (ones complement) and AND it with the shifted bit

        // fields. If the result is NON zero then there are bit(s) sticking

        // out the left hand end which means they are not contiguous



        DWORD TestField = pBitFields[iColour] >> PrefixBits;

        DWORD Mask = ULONG_MAX << SetBits;

        if (TestField & Mask) {

            NOTE1("Bit fields for component %d not contiguous",iColour);

            return FALSE;

        }

    }

    return TRUE;

}





// This counts the number of bits set in the input field



DWORD CImageDisplay::CountSetBits(DWORD Field)

{

    // This is a relatively well known bit counting algorithm



    DWORD Count = 0;

    DWORD init = Field;



    // Until the input is exhausted, count the number of bits



    while (init) {

        init = init & (init - 1);  // Turn off the bottommost bit

        Count++;

    }

    return Count;

}





// This counts the number of zero bits upto the first one set NOTE the input

// field should have been previously checked to ensure there is at least one

// set although if we don't find one set we return the impossible value 32



DWORD CImageDisplay::CountPrefixBits(DWORD Field)

{

    DWORD Mask = 1;

    DWORD Count = 0;



    while (TRUE) {

        if (Field & Mask) {

            return Count;

        }

        Count++;



        ASSERT(Mask != 0x80000000);

        if (Mask == 0x80000000) {

            return Count;

        }

        Mask <<= 1;

    }

}





// This is called to check the BITMAPINFOHEADER for the input type. There are

// many implicit dependancies between the fields in a header structure which

// if we validate now make for easier manipulation in subsequent handling. We

// also check that the BITMAPINFOHEADER matches it's specification such that

// fields likes the number of planes is one, that it's structure size is set

// correctly and that the bitmap dimensions have not been set as negative



BOOL CImageDisplay::CheckHeaderValidity(const VIDEOINFO *pInput)

{

    // Check the bitmap width and height are not negative.



    if (pInput->bmiHeader.biWidth <= 0 ||

    pInput->bmiHeader.biHeight <= 0) {

        NOTE("Invalid bitmap dimensions");

        return FALSE;

    }



    // Check the compression is either BI_RGB or BI_BITFIELDS



    if (pInput->bmiHeader.biCompression != BI_RGB) {

        if (pInput->bmiHeader.biCompression != BI_BITFIELDS) {

            NOTE("Invalid compression format");

            return FALSE;

        }

    }



    // If BI_BITFIELDS compression format check the colour depth



    if (pInput->bmiHeader.biCompression == BI_BITFIELDS) {

        if (pInput->bmiHeader.biBitCount != 16) {

            if (pInput->bmiHeader.biBitCount != 32) {

                NOTE("BI_BITFIELDS not 16/32 bit depth");

                return FALSE;

            }

        }

    }



    // Check the assumptions about the layout of the bit fields



    if (pInput->bmiHeader.biCompression == BI_BITFIELDS) {

        if (CheckBitFields(pInput) == FALSE) {

            NOTE("Bit fields are not valid");

            return FALSE;

        }

    }



    // Are the number of planes equal to one



    if (pInput->bmiHeader.biPlanes != 1) {

        NOTE("Number of planes not one");

        return FALSE;

    }



    // Check the image size is consistent (it can be zero)



    if (pInput->bmiHeader.biSizeImage != GetBitmapSize(&pInput->bmiHeader)) {

        if (pInput->bmiHeader.biSizeImage) {

            NOTE("Image size incorrectly set");

            return FALSE;

        }

    }



    // Check the size of the structure



    if (pInput->bmiHeader.biSize != sizeof(BITMAPINFOHEADER)) {

        NOTE("Size of BITMAPINFOHEADER wrong");

        return FALSE;

    }

    return CheckPaletteHeader(pInput);

}





// This runs a few simple tests against the palette fields in the input to

// see if it looks vaguely correct. The tests look at the number of palette

// colours present, the number considered important and the biCompression

// field which should always be BI_RGB as no other formats are meaningful



BOOL CImageDisplay::CheckPaletteHeader(const VIDEOINFO *pInput)

{

    // The checks here are for palettised videos only



    if (PALETTISED(pInput) == FALSE) {

        if (pInput->bmiHeader.biClrUsed) {

            NOTE("Invalid palette entries");

            return FALSE;

        }

        return TRUE;

    }



    // Compression type of BI_BITFIELDS is meaningless for palette video



    if (pInput->bmiHeader.biCompression != BI_RGB) {

        NOTE("Palettised video must be BI_RGB");

        return FALSE;

    }



    // Check the number of palette colours is correct



    if (pInput->bmiHeader.biClrUsed > PALETTE_ENTRIES(pInput)) {

        NOTE("Too many colours in palette");

        return FALSE;

    }



    // The number of important colours shouldn't exceed the number used



    if (pInput->bmiHeader.biClrImportant > pInput->bmiHeader.biClrUsed) {

        NOTE("Too many important colours");

        return FALSE;

    }

    return TRUE;

}





// Return the format of the video display



const VIDEOINFO *CImageDisplay::GetDisplayFormat()

{

    return &m_Display;

}





// Return TRUE if the display uses a palette



BOOL CImageDisplay::IsPalettised()

{

    return PALETTISED(&m_Display);

}





// Return the bit depth of the current display setting



WORD CImageDisplay::GetDisplayDepth()

{

    return m_Display.bmiHeader.biBitCount;

}





// Initialise the optional fields in a VIDEOINFO. These are mainly to do with

// the source and destination rectangles and palette information such as the

// number of colours present. It simplifies our code just a little if we don't

// have to keep checking for all the different valid permutations in a header

// every time we want to do anything with it (an example would be creating a

// palette). We set the base class media type before calling this function so

// that the media types between the pins match after a connection is made



HRESULT CImageDisplay::UpdateFormat(VIDEOINFO *pVideoInfo)

{

    ASSERT(pVideoInfo);



    BITMAPINFOHEADER *pbmi = HEADER(pVideoInfo);

    SetRectEmpty(&pVideoInfo->rcSource);

    SetRectEmpty(&pVideoInfo->rcTarget);



    // Set the number of colours explicitly



    if (PALETTISED(pVideoInfo)) {

        if (pVideoInfo->bmiHeader.biClrUsed == 0) {

            pVideoInfo->bmiHeader.biClrUsed = PALETTE_ENTRIES(pVideoInfo);

        }

    }



    // The number of important colours shouldn't exceed the number used, on

    // some displays the number of important colours is not initialised when

    // retrieving the display type so we set the colours used correctly



    if (pVideoInfo->bmiHeader.biClrImportant > pVideoInfo->bmiHeader.biClrUsed) {

        pVideoInfo->bmiHeader.biClrImportant = PALETTE_ENTRIES(pVideoInfo);

    }



    // Change the image size field to be explicit



    if (pVideoInfo->bmiHeader.biSizeImage == 0) {

        pVideoInfo->bmiHeader.biSizeImage = GetBitmapSize(&pVideoInfo->bmiHeader);

    }

    return NOERROR;

}





// Lots of video rendering filters want code to check proposed formats are ok

// This checks the VIDEOINFO we are passed as a media type. If the media type

// is a valid media type then we return NOERROR otherwise E_INVALIDARG. Note

// however we only accept formats that can be easily displayed in the display

// so if we are on a 16 bit device we will not accept 24 bit images. The one

// complexity is that most displays draw 8 bit palettised images efficiently

// Also if the input format is less colour bits per pixel then we also accept



HRESULT CImageDisplay::CheckVideoType(const VIDEOINFO *pInput)

{

    // First of all check the VIDEOINFOHEADER looks correct



    if (CheckHeaderValidity(pInput) == FALSE) {

        return E_INVALIDARG;

    }



    // Virtually all devices support palettised images efficiently



    if (m_Display.bmiHeader.biBitCount == pInput->bmiHeader.biBitCount) {

        if (PALETTISED(pInput) == TRUE) {

            ASSERT(PALETTISED(&m_Display) == TRUE);

            NOTE("(Video) Type connection ACCEPTED");

            return NOERROR;

        }

    }





    // Is the display depth greater than the input format



    if (m_Display.bmiHeader.biBitCount > pInput->bmiHeader.biBitCount) {

        NOTE("(Video) Mismatch agreed");

        return NOERROR;

    }



    // Is the display depth less than the input format



    if (m_Display.bmiHeader.biBitCount < pInput->bmiHeader.biBitCount) {

        NOTE("(Video) Format mismatch");

        return E_INVALIDARG;

    }





    // Both input and display formats are either BI_RGB or BI_BITFIELDS



    ASSERT(m_Display.bmiHeader.biBitCount == pInput->bmiHeader.biBitCount);

    ASSERT(PALETTISED(pInput) == FALSE);

    ASSERT(PALETTISED(&m_Display) == FALSE);



    // BI_RGB 16 bit representation is implicitly RGB555, and likewise BI_RGB

    // 24 bit representation is RGB888. So we initialise a pointer to the bit

    // fields they really mean and check against the display device format

    // This is only going to be called when both formats are equal bits pixel



    const DWORD *pInputMask = GetBitMasks(pInput);

    const DWORD *pDisplayMask = GetBitMasks((VIDEOINFO *)&m_Display);



    if (pInputMask[iRED] != pDisplayMask[iRED] ||

            pInputMask[iGREEN] != pDisplayMask[iGREEN] ||

                pInputMask[iBLUE] != pDisplayMask[iBLUE]) {



        NOTE("(Video) Bit field mismatch");

        return E_INVALIDARG;

    }



    NOTE("(Video) Type connection ACCEPTED");

    return NOERROR;

}





// Return the bit masks for the true colour VIDEOINFO provided



const DWORD *CImageDisplay::GetBitMasks(const VIDEOINFO *pVideoInfo)

{

    static const DWORD FailMasks[] = {0,0,0};



    if (pVideoInfo->bmiHeader.biCompression == BI_BITFIELDS) {

        return BITMASKS(pVideoInfo);

    }



    ASSERT(pVideoInfo->bmiHeader.biCompression == BI_RGB);



    switch (pVideoInfo->bmiHeader.biBitCount) {

        case 16: return bits555;

        case 24: return bits888;

        case 32: return bits888;

        default: return FailMasks;

    }

}





// Check to see if we can support media type pmtIn as proposed by the output

// pin - We first check that the major media type is video and also identify

// the media sub type. Then we thoroughly check the VIDEOINFO type provided

// As well as the contained VIDEOINFO being correct the major type must be

// video, the subtype a recognised video format and the type GUID correct



HRESULT CImageDisplay::CheckMediaType(const CMediaType *pmtIn)

{

    // Does this have a VIDEOINFOHEADER format block



    const GUID *pFormatType = pmtIn->FormatType();

    if (*pFormatType != FORMAT_VideoInfo) {

        NOTE("Format GUID not a VIDEOINFOHEADER");

        return E_INVALIDARG;

    }

    ASSERT(pmtIn->Format());



    // Check the format looks reasonably ok



    ULONG Length = pmtIn->FormatLength();

    if (Length < SIZE_VIDEOHEADER) {

        NOTE("Format smaller than a VIDEOHEADER");

        return E_FAIL;

    }



    VIDEOINFO *pInput = (VIDEOINFO *) pmtIn->Format();



    // Check the major type is MEDIATYPE_Video



    const GUID *pMajorType = pmtIn->Type();

    if (*pMajorType != MEDIATYPE_Video) {

        NOTE("Major type not MEDIATYPE_Video");

        return E_INVALIDARG;

    }



    // Check we can identify the media subtype



    const GUID *pSubType = pmtIn->Subtype();

    if (GetBitCount(pSubType) == USHRT_MAX) {

        NOTE("Invalid video media subtype");

        return E_INVALIDARG;

    }

    return CheckVideoType(pInput);

}





// Given a video format described by a VIDEOINFO structure we return the mask

// that is used to obtain the range of acceptable colours for this type, for

// example, the mask for a 24 bit true colour format is 0xFF in all cases. A

// 16 bit 5:6:5 display format uses 0xF8, 0xFC and 0xF8, therefore given any

// RGB triplets we can AND them with these fields to find one that is valid



BOOL CImageDisplay::GetColourMask(DWORD *pMaskRed,

                                  DWORD *pMaskGreen,

                                  DWORD *pMaskBlue)

{

    CAutoLock cDisplayLock(this);

    *pMaskRed = 0xFF;

    *pMaskGreen = 0xFF;

    *pMaskBlue = 0xFF;



    // If this format is palettised then it doesn't have bit fields



    if (m_Display.bmiHeader.biBitCount < 16) {

        return FALSE;

    }



    // If this is a 24 bit true colour display then it can handle all the

    // possible colour component ranges described by a byte. It is never

    // allowed for a 24 bit colour depth image to have BI_BITFIELDS set



    if (m_Display.bmiHeader.biBitCount == 24) {

        ASSERT(m_Display.bmiHeader.biCompression == BI_RGB);

        return TRUE;

    }



    // Calculate the mask based on the format's bit fields



    const DWORD *pBitFields = (DWORD *) GetBitMasks((VIDEOINFO *)&m_Display);

    DWORD *pOutputMask[] = { pMaskRed, pMaskGreen, pMaskBlue };



    // We know from earlier testing that there are no more than iMAXBITS

    // bits set in the mask and that they are all contiguous. All that

    // therefore remains is to shift them into the correct position



    for (INT iColour = iRED;iColour <= iBLUE;iColour++) {



        // This works out how many bits there are and where they live



        DWORD PrefixBits = CountPrefixBits(pBitFields[iColour]);

        DWORD SetBits = CountSetBits(pBitFields[iColour]);



        // The first shift moves the bit field so that it is right justified

        // in the DWORD, after which we then shift it back left which then

        // puts the leading bit in the bytes most significant bit position



        *(pOutputMask[iColour]) = pBitFields[iColour] >> PrefixBits;

        *(pOutputMask[iColour]) <<= (iMAXBITS - SetBits);

    }

    return TRUE;

}





/*  Helper to convert to VIDEOINFOHEADER2

*/

STDAPI ConvertVideoInfoToVideoInfo2(AM_MEDIA_TYPE *pmt)

{

    ASSERT(pmt->formattype == FORMAT_VideoInfo);

    VIDEOINFO *pVideoInfo = (VIDEOINFO *)pmt->pbFormat;

    PVOID pvNew = CoTaskMemAlloc(pmt->cbFormat + sizeof(VIDEOINFOHEADER2) -

                                 sizeof(VIDEOINFOHEADER));

    if (pvNew == NULL) {

        return E_OUTOFMEMORY;

    }

    CopyMemory(pvNew, pmt->pbFormat, FIELD_OFFSET(VIDEOINFOHEADER, bmiHeader));

    ZeroMemory((PBYTE)pvNew + FIELD_OFFSET(VIDEOINFOHEADER, bmiHeader),

               sizeof(VIDEOINFOHEADER2) - sizeof(VIDEOINFOHEADER));

    CopyMemory((PBYTE)pvNew + FIELD_OFFSET(VIDEOINFOHEADER2, bmiHeader),

               pmt->pbFormat + FIELD_OFFSET(VIDEOINFOHEADER, bmiHeader),

               pmt->cbFormat - FIELD_OFFSET(VIDEOINFOHEADER, bmiHeader));

    VIDEOINFOHEADER2 *pVideoInfo2 = (VIDEOINFOHEADER2 *)pvNew;

    pVideoInfo2->dwPictAspectRatioX = (DWORD)pVideoInfo2->bmiHeader.biWidth;

    pVideoInfo2->dwPictAspectRatioY = (DWORD)pVideoInfo2->bmiHeader.biHeight;

    pmt->formattype = FORMAT_VideoInfo2;

    CoTaskMemFree(pmt->pbFormat);

    pmt->pbFormat = (PBYTE)pvNew;

    pmt->cbFormat += sizeof(VIDEOINFOHEADER2) - sizeof(VIDEOINFOHEADER);

    return S_OK;

}

