www.pudn.com > RBGContrast.rar > contrast.cpp
//------------------------------------------------------------------------------ // File: Contrast.cpp // // Desc: DirectShow sample code - simple contrast-adjusting transform // filter. // // Copyright (c) Microsoft Corporation. All rights reserved. //------------------------------------------------------------------------------ // // // What this sample illustrates // // A simple transform filter that adjusts contrast on a video stream. We use // eight-bit palettised images exclusively and can increase or decrease the // contrast. The contrast is adjusted through a custom interface we support. // We have a property page that uses this interface to respond to user action. // // Summary // // This is a sample transform filter - we have a single input pin and also a // single output pin. The input pin accepts only palettised eight bit video // formats and our output pin also provides an eight bit palettised format. // We will normally be put between a video decoder (AVI or MPEG) and a video // renderer. We adjust the contrast of the video images sent through us, by // increasing contrast the colours become more saturated and likewise less // contrast reduces the colour into some grey shades and finally to all grey. // // // Implementation // // We adjust contrast using a neat trick with palettes: the colour palette of // an image effectively determines how the image is interpreted. So in short, // how the value 23 (for example) is turned into an RGB triplet for display. // By changing the palette we can reduce and increase contrast without doing // anything to the actual image pixels themselves (which makes us go faster). // So by adjusting the palette in-band we can adjust the pixel interpretion. // // The runtime video renderer supports palette changes in band, when we want // to change palettes we pick the new colours and attach a VIDEOINFO to the // next image we send to the renderer. It will then take that palette out of // the sample and realise a new palette in the window for us. The trigger to // change the palette is through a custom interface this filter provides. // // The custom interface we implement is called IContrast, this allows someone // to increase and decrease contrast levels for a given video. The normal way // for the interface to be used is to right click on the contrast filter in // GraphEdit and bring up our property page. This has a slider control on it // that can be used to adjust the contrast level. Each time the slider moves // it will ask for a contrast change by calling IContrast. To expose property // pages a filter implements the ISpecifyPropertyPages interface (it has only // one method called GetPages which returns the CLSIDs for the property pages) // // // Demonstration instructions // // Start GraphEdit, which is available in the SDK DXUtils folder. Drag and drop // an MPEG, AVI or MOV file into the tool and it will be rendered. Then go to // the filters in the graph and find the filter (box) titled "Video Renderer" // Disconnect the renderer from its source filter and insert a contrast in // between them (the contrast is added to the graph using the Graph Menu and // selecting Insert Filters, the filter is in the dialog box list). Once the // contrast filter is inserted run the graph. Right click on the contrast // box and select properties which brings up a property page that contains a // slider. The slider can be dragged up and down to adjust the contrast level // // // Files // // contprop.cpp Property page implementation with a slider control // contprop.h Class definition for the property page // contprop.rc Dialog box template for the property page // contrast.cpp Main contrast filter class implementation // contrast.def What APIs we import and export from this DLL // contrast.h Class definition for the contrast filter // contuids.h The transform filter CLSIDs // icontrst.h Defines the custom contrast filter interface // resource.h Microsoft Visual C++ generated resource file // // // Base classes we use // // CTransformFilter A generic transform filter that has a single input // and a single output pin. It has some PURE virtual // methods we must override (such as the Transform // method that does the real work). We also use it's // CUnknown base class to implement IUnknown for the // IContrast and ISpecifyPropertyPages we also support // // #include#include #include #include #if (1100 > _MSC_VER) #include #endif #include "contuids.h" #include "icontrst.h" #include "contprop.h" #include "contrast.h" #pragma warning(disable:4238) // nonstd extension used: class rvalue used as lvalue // setup data const AMOVIESETUP_MEDIATYPE sudPinTypes = { &MEDIATYPE_Video, // Major type &MEDIASUBTYPE_NULL // Minor type }; const AMOVIESETUP_PIN psudPins[] = { { L"Input", // String pin name FALSE, // Is it rendered FALSE, // Is it an output FALSE, // Allowed none FALSE, // Allowed many &CLSID_NULL, // Connects to filter L"Output", // Connects to pin 1, // Number of types &sudPinTypes }, // The pin details { L"Output", // String pin name FALSE, // Is it rendered TRUE, // Is it an output FALSE, // Allowed none FALSE, // Allowed many &CLSID_NULL, // Connects to filter L"Input", // Connects to pin 1, // Number of types &sudPinTypes // The pin details } }; const AMOVIESETUP_FILTER sudContrast = { &CLSID_Contrast, // Filter CLSID L"Video Contrast", // Filter name MERIT_DO_NOT_USE, // Its merit 2, // Number of pins psudPins // Pin details }; // List of class IDs and creator functions for the class factory. This // provides the link between the OLE entry point in the DLL and an object // being created. The class factory will call the static CreateInstance CFactoryTemplate g_Templates[2] = { { L"Video Contrast" , &CLSID_Contrast , CContrast::CreateInstance , NULL , &sudContrast } , { L"Video Contrast Property Page" , &CLSID_ContrastPropertyPage , CContrastProperties::CreateInstance } }; int g_cTemplates = sizeof(g_Templates) / sizeof(g_Templates[0]); // // Constructor // CContrast::CContrast(TCHAR *tszName,LPUNKNOWN punk,HRESULT *phr) : CTransformFilter(tszName, punk, CLSID_Contrast), m_DefaultContrastLevel(0), m_ContrastLevel(m_DefaultContrastLevel), m_PrevLevel(m_ContrastLevel), m_lBufferRequest(1) { ASSERT(tszName); ASSERT(phr); } // Contrast // // CreateInstance // // Provide the way for COM to create a CContrast object // CUnknown * WINAPI CContrast::CreateInstance(LPUNKNOWN punk, HRESULT *phr) { ASSERT(phr); CContrast *pNewObject = new CContrast(NAME("Contrast"), punk, phr); if (pNewObject == NULL) { if (phr) *phr = E_OUTOFMEMORY; } return pNewObject; } // CreateInstance // // NonDelegatingQueryInterface // // Reveals IContrast and ISpecifyPropertyPages // STDMETHODIMP CContrast::NonDelegatingQueryInterface(REFIID riid, void **ppv) { CheckPointer(ppv,E_POINTER); if (riid == IID_IContrast) { return GetInterface((IContrast *) this, ppv); } else if (riid == IID_ISpecifyPropertyPages) { return GetInterface((ISpecifyPropertyPages *) this, ppv); } else { return CTransformFilter::NonDelegatingQueryInterface(riid, ppv); } } // NonDelegatingQueryInterface // // Transform // // Copy the input sample into the output sample // Then transform the output sample 'in place' // HRESULT CContrast::Transform(IMediaSample *pIn, IMediaSample *pOut) { HRESULT hr = Copy(pIn, pOut); if (FAILED(hr)) { return hr; } return Transform(pOut); } // Transform // // Copy // // Make destination an identical copy of source // HRESULT CContrast::Copy(IMediaSample *pSource, IMediaSample *pDest) const { CheckPointer(pSource,E_POINTER); CheckPointer(pDest,E_POINTER); // Copy the sample data BYTE *pSourceBuffer, *pDestBuffer; long lSourceSize = pSource->GetActualDataLength(); #ifdef DEBUG long lDestSize = pDest->GetSize(); ASSERT(lDestSize >= lSourceSize); #endif pSource->GetPointer(&pSourceBuffer); pDest->GetPointer(&pDestBuffer); CopyMemory((PVOID) pDestBuffer,(PVOID) pSourceBuffer,lSourceSize); // Copy the sample times REFERENCE_TIME TimeStart, TimeEnd; if(NOERROR == pSource->GetTime(&TimeStart, &TimeEnd)) { pDest->SetTime(&TimeStart, &TimeEnd); } LONGLONG MediaStart, MediaEnd; if(pSource->GetMediaTime(&MediaStart,&MediaEnd) == NOERROR) { pDest->SetMediaTime(&MediaStart,&MediaEnd); } // Copy the Sync point property HRESULT hr = pSource->IsSyncPoint(); if(hr == S_OK) { pDest->SetSyncPoint(TRUE); } else if(hr == S_FALSE) { pDest->SetSyncPoint(FALSE); } else { // an unexpected error has occured... return E_UNEXPECTED; } // Copy the media type AM_MEDIA_TYPE *pMediaType; pSource->GetMediaType(&pMediaType); pDest->SetMediaType(pMediaType); DeleteMediaType(pMediaType); // Copy the preroll property hr = pSource->IsPreroll(); if(hr == S_OK) { pDest->SetPreroll(TRUE); } else if(hr == S_FALSE) { pDest->SetPreroll(FALSE); } else { // an unexpected error has occured... return E_UNEXPECTED; } // Copy the discontinuity property hr = pSource->IsDiscontinuity(); if(hr == S_OK) { pDest->SetDiscontinuity(TRUE); } else if(hr == S_FALSE) { pDest->SetDiscontinuity(FALSE); } else { // an unexpected error has occured... return E_UNEXPECTED; } // Copy the actual data length long lDataLength = pSource->GetActualDataLength(); pDest->SetActualDataLength(lDataLength); return NOERROR; } // Copy // // Transform // // 'In place' adjust the contrast of this sample // HRESULT CContrast::Transform(IMediaSample *pMediaSample) { CheckPointer(pMediaSample,E_POINTER); signed char ContrastLevel; // Take a copy, so we dont hold the lock for the whole transform. // Also ensures that we use the same level throughout this! { CAutoLock cAutoLock(&m_ContrastLock); ContrastLevel = m_ContrastLevel; } AM_MEDIA_TYPE *pAdjustedType = NULL; pMediaSample->GetMediaType(&pAdjustedType); if(pAdjustedType != NULL) { if(CheckInputType(&CMediaType(*pAdjustedType)) == S_OK) { m_pInput->CurrentMediaType() = *pAdjustedType; CoTaskMemFree(pAdjustedType); } else { CoTaskMemFree(pAdjustedType); return E_FAIL; } } // Pass on format changes to downstream filters if((pAdjustedType != NULL) || (m_PrevLevel != ContrastLevel)) { CMediaType AdjustedType((AM_MEDIA_TYPE) m_pInput->CurrentMediaType()); HRESULT hr = Transform(&AdjustedType, ContrastLevel); if(hr == S_OK) { pMediaSample->SetMediaType(&AdjustedType); m_PrevLevel = ContrastLevel; } else { return hr; } } return NOERROR; } // Transform // // CheckInputType // // Check the input type is OK, return an error otherwise // HRESULT CContrast::CheckInputType(const CMediaType *mtIn) { CheckPointer(mtIn,E_POINTER); // Check this is a VIDEOINFO type if(*mtIn->FormatType() != FORMAT_VideoInfo) { return E_INVALIDARG; } // Is this a palettised format if(CanChangeContrastLevel(mtIn)) { return NOERROR; } return E_FAIL; } // CheckInputType // // CheckTransform // // To be able to transform the formats must be identical // HRESULT CContrast::CheckTransform(const CMediaType *mtIn,const CMediaType *mtOut) { CheckPointer(mtIn,E_POINTER); CheckPointer(mtOut,E_POINTER); HRESULT hr; if(FAILED(hr = CheckInputType(mtIn))) { return hr; } // format must be a VIDEOINFOHEADER if(*mtOut->FormatType() != FORMAT_VideoInfo) { return E_INVALIDARG; } // formats must be big enough if(mtIn->FormatLength() < sizeof(VIDEOINFOHEADER) || mtOut->FormatLength() < sizeof(VIDEOINFOHEADER)) return E_INVALIDARG; VIDEOINFO *pInput = (VIDEOINFO *) mtIn->Format(); VIDEOINFO *pOutput = (VIDEOINFO *) mtOut->Format(); if(memcmp(&pInput->bmiHeader,&pOutput->bmiHeader,sizeof(BITMAPINFOHEADER)) == 0) { return NOERROR; } return E_INVALIDARG; } // CheckTransform // // DecideBufferSize // // Tell the output pin's allocator what size buffers we // require. Can only do this when the input is connected // HRESULT CContrast::DecideBufferSize(IMemAllocator *pAlloc,ALLOCATOR_PROPERTIES *pProperties) { CheckPointer(pAlloc,E_POINTER); CheckPointer(pProperties,E_POINTER); // Is the input pin connected if(m_pInput->IsConnected() == FALSE) { return E_UNEXPECTED; } HRESULT hr = NOERROR; pProperties->cBuffers = 1; pProperties->cbBuffer = m_pInput->CurrentMediaType().GetSampleSize(); ASSERT(pProperties->cbBuffer); // If we don't have fixed sized samples we must guess some size if(!m_pInput->CurrentMediaType().bFixedSizeSamples) { if(pProperties->cbBuffer < 100000) { // nothing more than a guess!! pProperties->cbBuffer = 100000; } } // Ask the allocator to reserve us some sample memory, NOTE the function // can succeed (that is return NOERROR) but still not have allocated the // memory that we requested, so we must check we got whatever we wanted ALLOCATOR_PROPERTIES Actual; hr = pAlloc->SetProperties(pProperties,&Actual); if(FAILED(hr)) { return hr; } ASSERT(Actual.cBuffers == 1); if(pProperties->cBuffers > Actual.cBuffers || pProperties->cbBuffer > Actual.cbBuffer) { return E_FAIL; } return NOERROR; } // DecideBufferSize // // GetMediaType // // I support one type, namely the type of the input pin // We must be connected to support the single output type // HRESULT CContrast::GetMediaType(int iPosition, CMediaType *pMediaType) { // Is the input pin connected if(m_pInput->IsConnected() == FALSE) { return E_UNEXPECTED; } // This should never happen if(iPosition < 0) { return E_INVALIDARG; } // Do we have more items to offer if(iPosition > 0) { return VFW_S_NO_MORE_ITEMS; } CheckPointer(pMediaType,E_POINTER); *pMediaType = m_pInput->CurrentMediaType(); return NOERROR; } // GetMediaType // // get_ContrastLevel // // Return the current contrast level // STDMETHODIMP CContrast::get_ContrastLevel(signed char *pContrastLevel) { CheckPointer(pContrastLevel,E_POINTER); CAutoLock cAutoLock(&m_ContrastLock); *pContrastLevel = m_ContrastLevel; return NOERROR; } // get_ContrastLevel // // put_ContrastLevel // // Sets the contrast level for the filter // STDMETHODIMP CContrast::put_ContrastLevel(signed char ContrastLevel, unsigned long ChangeTime) { CAutoLock cAutoLock(&m_ContrastLock); m_ContrastLevel = ContrastLevel; return NOERROR; } // put_ContrastLevel // // put_DefaultContrastLevel // // Sets the default contrast level for the filter // STDMETHODIMP CContrast::put_DefaultContrastLevel() { CAutoLock cAutoLock(&m_ContrastLock); m_ContrastLevel = m_DefaultContrastLevel; return NOERROR; } // put_DefaultContrastLevel // // GetPages // // This is the sole member of ISpecifyPropertyPages // Returns the clsid's of the property pages we support // STDMETHODIMP CContrast::GetPages(CAUUID *pPages) { CheckPointer(pPages,E_POINTER); pPages->cElems = 1; pPages->pElems = (GUID *) CoTaskMemAlloc(sizeof(GUID)); if(pPages->pElems == NULL) { return E_OUTOFMEMORY; } *(pPages->pElems) = CLSID_ContrastPropertyPage; return NOERROR; } // GetPages // // CanChangeContrastLevel // // Check if this is a paletised format // BOOL CContrast::CanChangeContrastLevel(const CMediaType *pMediaType) const { CheckPointer(pMediaType,FALSE); if((IsEqualGUID(*pMediaType->Type(), MEDIATYPE_Video)) && (IsEqualGUID(*pMediaType->Subtype(), MEDIASUBTYPE_RGB8))) { // I think I can process this format (8 bit palettised) // So do a quick sanity check on the palette information VIDEOINFO *pvi = (VIDEOINFO *) pMediaType->Format(); return (pvi->bmiHeader.biBitCount == 8); } else { return FALSE; } } // CanChangeContrastLevel // // Transform // // Adjust the palette entries of pType to reflect the specified contrast level // HRESULT CContrast::Transform(AM_MEDIA_TYPE *pType, const signed char ContrastLevel) const { CheckPointer(pType,E_POINTER); VIDEOINFO *pvi = (VIDEOINFO *) pType->pbFormat; if(ContrastLevel >= 0) { int Low = 0 + m_ContrastLevel; int High = 255 - m_ContrastLevel; float Grad = ((float)(High - Low)) / 255; for(UINT i = 0; i < pvi->bmiHeader.biClrUsed; i++) { IncreaseContrast(&pvi->bmiColors[i], Low, High, Grad); } } else { float Grad = 255 / (255 + (float) ContrastLevel + (float) ContrastLevel); for(UINT i = 0; i < pvi->bmiHeader.biClrUsed; i++) { DecreaseContrast(&pvi->bmiColors[i], ContrastLevel, Grad); } } return NOERROR; } // Transform // // IncreaseContrast // // Adjust the contrast of this palette element // inline void CContrast::IncreaseContrast(RGBQUAD *pElem, const int Low, const int High, const float Grad) const { ASSERT(pElem); IncreaseContrast(&pElem->rgbRed, Low, High, Grad); IncreaseContrast(&pElem->rgbGreen, Low, High, Grad); IncreaseContrast(&pElem->rgbBlue, Low, High, Grad); } // IncreaseContrast // // IncreaseContrast // // Change this byte's contrast // inline void CContrast::IncreaseContrast(BYTE *pByte, const int Low, const int High, const float Grad) const { ASSERT(pByte); if(*pByte <= Low) { *pByte = 0; } else if((Low < *pByte) && (*pByte < High)) { *pByte = (BYTE)( (*pByte - Low) / Grad); } else { // pElem->rgbGreen >= High *pByte = 255; } } // IncreaseContrast // // DecreaseContrast // // Adjust the contrast of this palette element // inline void CContrast::DecreaseContrast(RGBQUAD *pElem, const int Level, const float Grad) const { ASSERT(pElem); DecreaseContrast(&pElem->rgbRed, Level, Grad); DecreaseContrast(&pElem->rgbGreen, Level, Grad); DecreaseContrast(&pElem->rgbBlue, Level, Grad); } // DecreaseContrast // // DecreaseContrast // // Use different maths to calculate the 'decreasing contrast' line // inline void CContrast::DecreaseContrast(BYTE *pByte, const int Level, const float Grad) const { ASSERT(pByte); ASSERT(Grad != 0.0); *pByte = (BYTE) ( ((int) (*pByte / Grad)) - Level); } // DecreaseContrast //////////////////////////////////////////////////////////////////////// // // Exported entry points for registration and unregistration // (in this case they only call through to default implementations). // //////////////////////////////////////////////////////////////////////// // // DllRegisterServer // // Handle registration of this filter // STDAPI DllRegisterServer() { return AMovieDllRegisterServer2(TRUE); } // DllRegisterServer // // DllUnregisterServer // STDAPI DllUnregisterServer() { return AMovieDllRegisterServer2(FALSE); } // DllUnregisterServer // // DllEntryPoint // extern "C" BOOL WINAPI DllEntryPoint(HINSTANCE, ULONG, LPVOID); BOOL APIENTRY DllMain(HANDLE hModule, DWORD dwReason, LPVOID lpReserved) { return DllEntryPoint((HINSTANCE)(hModule), dwReason, lpReserved); }