WINUTIL.CPP

//==========================================================================; 
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
// KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
// PURPOSE.
//
// Copyright (c) 1992 - 1997 Microsoft Corporation. All Rights Reserved.
//
//--------------------------------------------------------------------------;

// Generic window handler base class, December 1995

#include <streams.h>
#include <limits.h>

static UINT MsgDestroy;

// Constructor

CBaseWindow::CBaseWindow(BOOL bDoGetDC) :
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_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);
m_ShowStageTop = RegisterWindowMessage(SHOWSTAGETOP);
m_RealizePalette = RegisterWindowMessage(REALIZEPALETTE);

return DoCreateWindow();
}


// Destructor just a placeholder so that we know it becomes virtual
// Derived classes MUST call DoneWithWindow in their destructors so
// that no messages arrive after the derived class constructor ends

#ifdef DEBUG
CBaseWindow::~CBaseWindow()
{
ASSERT(m_hwnd == NULL);
ASSERT(m_hdc == NULL);
}
#endif


// We use the sync worker event to have the window destroyed. All we do is
// signal the event and wait on the window thread handle. Trying to send it
// messages causes too many problems, furthermore to be on the safe side we
// just wait on the thread handle while it returns WAIT_TIMEOUT or there is
// a sent message to process on this thread. If the constructor failed to
// create the thread in the first place then the loop will get terminated

HRESULT CBaseWindow::DoneWithWindow()
{
if (GetWindowThreadProcessId(m_hwnd, NULL) != GetCurrentThreadId()) {
MsgDestroy = RegisterWindowMessage(TEXT("AM_DESTROY"));
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;
}


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

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);
ASSERT(hPalette);
m_hPalette = hPalette;
}
return SetPalette();
}

HRESULT CBaseWindow::SetPalette()
{
if (!m_bNoRealize) {
SendMessage(m_hwnd, m_RealizePalette, 0, 0);
// Make sure the device's palette is flushed
return (GdiFlush() == FALSE ? S_FALSE : S_OK);
} else {
// Just select the palette
ASSERT(m_hdc);
ASSERT(m_MemoryDC);
SelectPalette(m_hdc,m_hPalette,m_bBackground);
SelectPalette(m_MemoryDC,m_hPalette,m_bBackground);
return S_OK;
}
}

// Realise our palettes in the window and device contexts

HRESULT CBaseWindow::DoRealisePalette(BOOL bForceBackground)
{
// 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
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);
EXECUTE_ASSERT(RealizePalette(m_hdc) != GDI_ERROR);
SelectPalette(m_MemoryDC,m_hPalette,m_bBackground);
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 *)GetWindowLong(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

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
BOOL rc = SetWindowLong(hwnd, (DWORD) 0, (LONG) 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();
return 0;
}
return pBaseWindow->OnReceiveMessage(hwnd,uMsg,wParam,lParam);
}


// When the window size changes we adjust our member variables that
// contain the dimensions of the client rectangle for our window so
// that we come to render an image we will know whether to stretch

BOOL CBaseWindow::OnSize(LONG Width, LONG Height)
{
m_Width = Width;
m_Height = Height;
return TRUE;
}


// This function handles the WM_CLOSE message

BOOL CBaseWindow::OnClose()
{
ShowWindow(m_hwnd,SW_HIDE);
return TRUE;
}


// This is called by the worker window thread when it receives a terminate
// message from the window object destructor to delete all the resources we
// allocated during initialisation. By the time the worker thread exits all
// processing will have been completed as the source filter disconnection
// flushes the image pending sample, therefore the GdiFlush should succeed

HRESULT CBaseWindow::UninitialiseWindow()
{
// Have we already cleaned up

if (m_hwnd == NULL) {
ASSERT(m_hdc == NULL);
ASSERT(m_MemoryDC == NULL);
return NOERROR;
}

// Release the window resources

EXECUTE_ASSERT(GdiFlush());

if (m_hdc)
{
EXECUTE_ASSERT(ReleaseDC(m_hwnd,m_hdc));
m_hdc = NULL;
}

if (m_MemoryDC)
{
EXECUTE_ASSERT(DeleteDC(m_MemoryDC));
m_MemoryDC = NULL;
}

// Reset the window variables
m_hwnd = NULL;

return NOERROR;
}


// This is called by the worker window thread after it has created the main
// window and it wants to initialise the rest of the owner objects window
// variables such as the device contexts. We execute this function with the
// critical section still locked. Nothing in this function must generate any
// SendMessage calls to the window because this is executing on the window
// thread so the message will never be processed and we will deadlock

HRESULT CBaseWindow::InitialiseWindow(HWND hwnd)
{
// Initialise the window variables

ASSERT(IsWindow(hwnd));
m_hwnd = hwnd;

if (m_bDoGetDC)
{
EXECUTE_ASSERT(m_hdc = GetDC(hwnd));
EXECUTE_ASSERT(m_MemoryDC = CreateCompatibleDC(m_hdc));

EXECUTE_ASSERT(SetStretchBltMode(m_hdc,COLORONCOLOR));
EXECUTE_ASSERT(SetStretchBltMode(m_MemoryDC,COLORONCOLOR));
}

return NOERROR;
}

HRESULT CBaseWindow::DoCreateWindow()
{
WNDCLASS wndclass; // Used to register classes
BOOL bRegistered; // Is this class registered
HWND hwnd; // Handle to our window

bRegistered = GetClassInfo(m_hInstance, // Module instance
m_pClassName, // Window class
&wndclass); // Info structure

// if the window is to be used for drawing puposes and we are getting a DC
// for the entire lifetime of the window then changes the class style to do
// say so. If we don't set this flag then the DC comes from the cache and is
// really bad.
if (m_bDoGetDC)
{
m_ClassStyles |= CS_OWNDC;
}

if (bRegistered == FALSE) {

// Register the renderer window class

wndclass.lpszClassName = m_pClassName;
wndclass.style = m_ClassStyles;
wndclass.lpfnWndProc = (WNDPROC) 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 HRESULT_FROM_WIN32(Error);
}

// Check the window LONG is the object who created us
ASSERT(GetWindowLong(hwnd, 0) == (LONG)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);

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


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


// 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("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)
{
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;
}


// 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),
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 HRESULT_FROM_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 HRESULT_FROM_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 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);

// 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()
{
// Do we have a palette to remove

if (m_hPalette == NULL) {
return NOERROR;
}

// Get a standard VGA colour palette

HPALETTE hPalette = (HPALETTE) GetStockObject(DEFAULT_PALETTE);
ASSERT(hPalette);
const HPALETTE hPalOurs = m_hPalette;

// Install the standard palette and delete ours. As in the previous method
// we may not have been given a window in the constructor to use and if we
// didn't then don't try to install the stock palette in it. This is used
// by filters that have to create palettes but who do not draw using GDI

if (m_pBaseWindow) {
SelectPalette(m_pBaseWindow->GetWindowHDC(), hPalette, TRUE);
SelectPalette(m_pBaseWindow->GetMemoryHDC(), hPalette, TRUE);
}

EXECUTE_ASSERT(DeleteObject(hPalOurs));
m_hPalette = NULL;
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 >= 0);
ASSERT(pVideoInfo->bmiHeader.biClrUsed <= iPALETTE_COLORS);
BITMAPINFOHEADER *pHeader = HEADER(pVideoInfo);

const RGBQUAD *pColours; // Pointer to the palette
LOGPALETTE *lp; // Used to create a palette
HPALETTE hPalette; // Logical palette object

lp = (LOGPALETTE *) new BYTE[sizeof(LOGPALETTE) + SIZE_PALETTE];
if (lp == NULL) {
return NULL;
}

// Unfortunately for some hare brained reason a GDI palette entry (a
// PALETTEENTRY structure) is different to a palette entry from a DIB
// format (a RGBQUAD structure) so we have to do the field conversion
// The VIDEOINFO containing the palette may be a true colour type so
// we use GetBitmapPalette to skip over any bit fields if they exist

lp->palVersion = PALVERSION;
lp->palNumEntries = (USHORT) pHeader->biClrUsed;
if (lp->palNumEntries == 0) lp->palNumEntries = (1 << pHeader->biBitCount);
pColours = GetBitmapPalette(pVideoInfo);

for (DWORD dwCount = 0;dwCount < lp->palNumEntries;dwCount++) {
lp->palPalEntry[dwCount].peRed = pColours[dwCount].rgbRed;
lp->palPalEntry[dwCount].peGreen = pColours[dwCount].rgbGreen;
lp->palPalEntry[dwCount].peBlue = pColours[dwCount].rgbBlue;
lp->palPalEntry[dwCount].peFlags = 0;
}

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

// Create a logical palette

hPalette = CreatePalette(lp);
ASSERT(hPalette != NULL);
delete[] lp;
return hPalette;
}


// GDI does a fair job of compressing the palette entries you give it, so for
// example if you have five entries with an RGB colour (0,0,0) it will remove
// all but one of them. When you subsequently draw an image it will map from
// your logical palette to the compressed device palette. This function looks
// to see if it is trying to be an identity palette and if so sets the flags
// field in the PALETTEENTRYs so they remain expanded to boost performance

// We can be passed an optional device name if we wish to prepare a palette
// for a specific monitor on a multi monitor system

HRESULT CImagePalette::MakeIdentityPalette(PALETTEENTRY *pEntry,INT iColours, LPSTR szDevice)
{
PALETTEENTRY SystemEntries[10]; // System palette entries
BOOL bIdentityPalette = TRUE; // Is an identity palette
ASSERT(iColours <= iPALETTE_COLORS); // Should have a palette
const int PalLoCount = 10; // First ten reserved colours
const int PalHiStart = 246; // Last VGA palette entries

// Does this have the full colour range

if (iColours < 10) {
return S_FALSE;
}

// Apparently some displays have odd numbers of system colours

// Get a DC on the right monitor - it's ugly, but this is the way you have
// to do it
HDC hdc;
if (szDevice == NULL || lstrcmpiA(szDevice, "DISPLAY") == 0)
hdc = CreateDCA("DISPLAY", NULL, NULL, NULL);
else
hdc = CreateDCA(NULL, szDevice, NULL, NULL);
ASSERT(hdc);
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 %s"),
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);
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;
}