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