//==========================================================================; // // THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY // KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR // PURPOSE. // // Copyright (c) 1992 - 1998 Microsoft Corporation. All Rights Reserved. // //--------------------------------------------------------------------------; // // // What this sample illustrates // // A special effects filter that does image filtering and colour changes. We // work on decompressed true colour images and apply a range of effects. The // effect we do and when it is applied to the video stream can be configured // through a custom interface we implement and which our property page uses. // We can also save/load our property settings by supporting IPersistStream // // // Summary // // This is a sample special effects filter - it works only with RGB24 formats // We can be inserted between video decoders and renderers and apply a number // of different effects to the decompressed video stream. For example we can // apply red, green or blue filters to the images. We also have posterise and // greying effects amongst others. The effect can be applied to the stream at // a given start and end media time, this is defined by using the IIPEffect // interface and can be viewed by using the property page this filter supports // // // Demonstration instructions // // Start GRAPHEDT available in the ActiveMovie SDK tools. Drag and drop any // MPEG, AVI or MOV file into the tool and it will be rendered. Then go to // the video renderer box and disconnect its input pin from the filter it is // connected to. Go to the Graph menu and select Insert Filters, from the // dialog select the Special Effects filter and dismiss the dialog. back in // the tool workspace connect the output of the decoder to the input of the // sample filter and then connect the output of the sample to the input of // the video renderer (GRAPHEDT may put a colour space convertor in between) // Then press play and then right click on the sample to select a transform // by default a transform will be applied from 2 to 7 seconds into the movie // // // Files // // ezprop.cpp A property page to control the video effects // ezprop.h Class definition for the property page object // ezprop.rc Dialog box template for the property page // ezrgb24.cpp Main filter code that does the special effects // ezrgb24.def What APIs we import and export from this DLL // ezrgb24.h Class definition for the special effects filter // ezuids.h Header file containing the filter CLSIDs // iez.h Defines the special effects custom interface // makefile How we build it... // resource.h Microsoft Visual C++ generated resource file // // // Base classes used // // CTransformFilter A transform filter with one input and output pin // CPersistStream Handles the grunge of supporting IPersistStream // // #include #include #include #if (1100 > _MSC_VER) #include #else #include #endif #include "EZuids.h" #include "iEZ.h" #include "EZprop.h" #include "EZrgb24.h" #include "resource.h" // Setup information const AMOVIESETUP_MEDIATYPE sudPinTypes = { &MEDIATYPE_Video, // Major type &MEDIASUBTYPE_NULL // Minor type }; const AMOVIESETUP_PIN sudpPins[] = { { L"Input", // Pins string name FALSE, // Is it rendered FALSE, // Is it an output FALSE, // Are we allowed none FALSE, // And allowed many &CLSID_NULL, // Connects to filter NULL, // Connects to pin 1, // Number of types &sudPinTypes // Pin information }, { L"Output", // Pins string name FALSE, // Is it rendered TRUE, // Is it an output FALSE, // Are we allowed none FALSE, // And allowed many &CLSID_NULL, // Connects to filter NULL, // Connects to pin 1, // Number of types &sudPinTypes // Pin information } }; const AMOVIESETUP_FILTER sudEZrgb24 = { &CLSID_EZrgb24, // Filter CLSID L"Image Effects", // String name MERIT_DO_NOT_USE, // Filter merit 2, // Number of pins sudpPins // Pin information }; // 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[] = { { L"Image Effects" , &CLSID_EZrgb24 , CEZrgb24::CreateInstance , NULL , &sudEZrgb24 } , { L"Special Effects" , &CLSID_EZrgb24PropertyPage , CEZrgb24Properties::CreateInstance } }; int g_cTemplates = sizeof(g_Templates) / sizeof(g_Templates[0]); // // DllRegisterServer // // Handles sample registry and unregistry // STDAPI DllRegisterServer() { return AMovieDllRegisterServer2( TRUE ); } // DllRegisterServer // // DllUnregisterServer // STDAPI DllUnregisterServer() { return AMovieDllRegisterServer2( FALSE ); } // DllUnregisterServer // // Constructor // CEZrgb24::CEZrgb24(TCHAR *tszName, LPUNKNOWN punk, HRESULT *phr) : CTransformFilter(tszName, punk, CLSID_EZrgb24), m_effect(IDC_RED), m_lBufferRequest(1), CPersistStream(punk, phr) { char sz[60]; GetProfileStringA("Quartz", "EffectStart", "2.0", sz, 60); m_effectStartTime = COARefTime(atof(sz)); GetProfileStringA("Quartz", "EffectLength", "5.0", sz, 60); m_effectTime = COARefTime(atof(sz)); } // (Constructor) // // CreateInstance // // Provide the way for COM to create a EZrgb24 object // CUnknown *CEZrgb24::CreateInstance(LPUNKNOWN punk, HRESULT *phr) { CEZrgb24 *pNewObject = new CEZrgb24(NAME("Image Effects"), punk, phr); if (pNewObject == NULL) { *phr = E_OUTOFMEMORY; } return pNewObject; } // CreateInstance // // NonDelegatingQueryInterface // // Reveals IIPEffect and ISpecifyPropertyPages // STDMETHODIMP CEZrgb24::NonDelegatingQueryInterface(REFIID riid, void **ppv) { CheckPointer(ppv,E_POINTER); if (riid == IID_IIPEffect) { return GetInterface((IIPEffect *) 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'. If we have all keyframes, then we shouldn't do a copy // If we have cinepak or indeo and are decompressing frame N it needs frame // decompressed frame N-1 available to calculate it, unless we are at a // keyframe. So with keyframed codecs, you can't get away with applying the // transform to change the frames in place, because you'll screw up the next // frames decompression. The runtime MPEG decoder does not have keyframes in // the same way so it can be done in place. We know if a sample is key frame // as we transform because the sync point property will be set on the sample // HRESULT CEZrgb24::Transform(IMediaSample *pIn, IMediaSample *pOut) { // Copy the properties across HRESULT hr = Copy(pIn, pOut); if (FAILED(hr)) { return hr; } // Check to see if it is time to do the sample CRefTime tStart, tStop ; pIn->GetTime((REFERENCE_TIME *) &tStart, (REFERENCE_TIME *) &tStop); if (tStart >= m_effectStartTime) { if (tStop <= (m_effectStartTime + m_effectTime)) { return Transform(pOut); } } return NOERROR; } // Transform // // Copy // // Make destination an identical copy of source // HRESULT CEZrgb24::Copy(IMediaSample *pSource, IMediaSample *pDest) const { // Copy the sample data BYTE *pSourceBuffer, *pDestBuffer; long lSourceSize = pSource->GetActualDataLength(); long lDestSize = pDest->GetSize(); ASSERT(lDestSize >= lSourceSize); 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) // // 'In place' apply the image effect to this sample // HRESULT CEZrgb24::Transform(IMediaSample *pMediaSample) { AM_MEDIA_TYPE* pType = &m_pInput->CurrentMediaType(); VIDEOINFOHEADER *pvi = (VIDEOINFOHEADER *) pType->pbFormat; BYTE *pData; // Pointer to the actual image buffer long lDataLen; // Holds length of any given sample unsigned int grey,grey2; // Used when applying greying effects int iPixel; // Used to loop through the image pixels int temp,x,y; // General loop counters for transforms RGBTRIPLE *prgb; // Holds a pointer to the current pixel pMediaSample->GetPointer(&pData); lDataLen = pMediaSample->GetSize(); // Get the image properties from the BITMAPINFOHEADER int iPixelSize = pvi->bmiHeader.biBitCount / 8; int cxImage = pvi->bmiHeader.biWidth; int cyImage = pvi->bmiHeader.biHeight; int cbImage = cyImage * cxImage * iPixelSize; int numPixels = cxImage * cyImage; switch (m_effect) { case IDC_NONE: break; // Zero out the green and blue components to leave only the red // so acting as a filter - for better visual results, compute a // greyscale value for the pixel and make that the red component case IDC_RED: prgb = (RGBTRIPLE*) pData; for (iPixel=0; iPixel < numPixels; iPixel++, prgb++) { prgb->rgbtGreen = 0; prgb->rgbtBlue = 0; } break; case IDC_GREEN: prgb = (RGBTRIPLE*) pData; for (iPixel=0; iPixel < numPixels; iPixel++, prgb++) { prgb->rgbtRed = 0; prgb->rgbtBlue = 0; } break; case IDC_BLUE: prgb = (RGBTRIPLE*) pData; for (iPixel=0; iPixel < numPixels; iPixel++, prgb++) { prgb->rgbtRed = 0; prgb->rgbtGreen = 0; } break; // Bitwise shift each component to the right by 1 // this results in the image getting much darker case IDC_DARKEN: prgb = (RGBTRIPLE*) pData; for (iPixel=0; iPixel < numPixels; iPixel++, prgb++) { prgb->rgbtRed = prgb->rgbtRed >> 1; prgb->rgbtGreen = prgb->rgbtGreen >> 1; prgb->rgbtBlue = prgb->rgbtBlue >> 1; } break; // Toggle each bit - this gives a sort of X-ray effect case IDC_XOR: prgb = (RGBTRIPLE*) pData; for (iPixel=0; iPixel < numPixels; iPixel++, prgb++) { prgb->rgbtRed = prgb->rgbtRed ^ 0xff; prgb->rgbtGreen = prgb->rgbtGreen ^ 0xff; prgb->rgbtBlue = prgb->rgbtBlue ^ 0xff; } break; // Zero out the five LSB per each component case IDC_POSTERIZE: prgb = (RGBTRIPLE*) pData; for (iPixel=0; iPixel < numPixels; iPixel++, prgb++) { prgb->rgbtRed = prgb->rgbtRed & 0xe0; prgb->rgbtGreen = prgb->rgbtGreen & 0xe0; prgb->rgbtBlue = prgb->rgbtBlue & 0xe0; } break; // Take pixel and its neighbor two pixels to the right and average // then out - this blurs them and produces a subtle motion effect case IDC_BLUR: prgb = (RGBTRIPLE*) pData; for (y = 0 ; y < pvi->bmiHeader.biHeight; y++) { for (x = 2 ; x < pvi->bmiHeader.biWidth; x++,prgb++) { prgb->rgbtRed = (prgb->rgbtRed + prgb[2].rgbtRed) >> 1; prgb->rgbtGreen = (prgb->rgbtGreen + prgb[2].rgbtGreen) >> 1; prgb->rgbtBlue = (prgb->rgbtBlue + prgb[2].rgbtBlue) >> 1; } prgb +=2; } break; // An excellent greyscale calculation is: // grey = (30 * red + 59 * green + 11 * blue) / 100 // This is a bit too slow so a faster calculation is: // grey = (red + green) / 2 case IDC_GREY: prgb = (RGBTRIPLE*) pData; for (iPixel=0; iPixel < numPixels ; iPixel++, prgb++) { grey = (prgb->rgbtRed + prgb->rgbtGreen) >> 1; prgb->rgbtRed = prgb->rgbtGreen = prgb->rgbtBlue = (BYTE) grey; } break; // Really sleazy emboss - rather than using a nice 3x3 convulution // matrix, we compare the greyscale values of two neighbours. If // they are not different, then a mid grey (128, 128, 128) is // supplied. Large differences get father away from the mid grey case IDC_EMBOSS: prgb = (RGBTRIPLE*) pData; for (y = 0 ; y < pvi->bmiHeader.biHeight; y++) { grey2 = (prgb->rgbtRed + prgb->rgbtGreen) >> 1; prgb->rgbtRed = prgb->rgbtGreen = prgb->rgbtBlue = (BYTE) 128; prgb++; for (x = 1 ; x < pvi->bmiHeader.biWidth; x++) { grey = (prgb->rgbtRed + prgb->rgbtGreen) >> 1; temp = grey - grey2; if (temp > 127) temp = 127; if (temp < -127) temp = -127; temp += 128; prgb->rgbtRed = prgb->rgbtGreen = prgb->rgbtBlue = (BYTE) temp; grey2 = grey; prgb++; } } break; } return NOERROR; } // Transform (in place) // Check the input type is OK - return an error otherwise HRESULT CEZrgb24::CheckInputType(const CMediaType *mtIn) { // check this is a VIDEOINFOHEADER type if (*mtIn->FormatType() != FORMAT_VideoInfo) { return E_INVALIDARG; } // Can we transform this type if (CanPerformEZrgb24(mtIn)) { return NOERROR; } return E_FAIL; } // // Checktransform // // Check a transform can be done between these formats // HRESULT CEZrgb24::CheckTransform(const CMediaType *mtIn, const CMediaType *mtOut) { if (CanPerformEZrgb24(mtIn)) { if (*mtIn == *mtOut) { return NOERROR; } } return E_FAIL; } // CheckTransform // // DecideBufferSize // // Tell the output pin's allocator what size buffers we // require. Can only do this when the input is connected // HRESULT CEZrgb24::DecideBufferSize(IMemAllocator *pAlloc,ALLOCATOR_PROPERTIES *pProperties) { // Is the input pin connected if (m_pInput->IsConnected() == FALSE) { return E_UNEXPECTED; } ASSERT(pAlloc); ASSERT(pProperties); HRESULT hr = NOERROR; pProperties->cBuffers = 1; pProperties->cbBuffer = m_pInput->CurrentMediaType().GetSampleSize(); ASSERT(pProperties->cbBuffer); // 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 // This type is only available if my input is connected // HRESULT CEZrgb24::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; } *pMediaType = m_pInput->CurrentMediaType(); return NOERROR; } // GetMediaType // // CanPerformEZrgb24 // // Check if this is a RGB24 true colour format // BOOL CEZrgb24::CanPerformEZrgb24(const CMediaType *pMediaType) const { if (IsEqualGUID(*pMediaType->Type(), MEDIATYPE_Video)) { if (IsEqualGUID(*pMediaType->Subtype(), MEDIASUBTYPE_RGB24)) { VIDEOINFOHEADER *pvi = (VIDEOINFOHEADER *) pMediaType->Format(); return (pvi->bmiHeader.biBitCount == 24); } } return FALSE; } // CanPerformEZrgb24 #define WRITEOUT(var) hr = pStream->Write(&var, sizeof(var), NULL); \ if (FAILED(hr)) return hr; #define READIN(var) hr = pStream->Read(&var, sizeof(var), NULL); \ if (FAILED(hr)) return hr; // // GetClassID // // This is the only method of IPersist // STDMETHODIMP CEZrgb24::GetClassID(CLSID *pClsid) { return CBaseFilter::GetClassID(pClsid); } // GetClassID // // ScribbleToStream // // Overriden to write our state into a stream // HRESULT CEZrgb24::ScribbleToStream(IStream *pStream) { HRESULT hr; WRITEOUT(m_effect); WRITEOUT(m_effectStartTime); WRITEOUT(m_effectTime); return NOERROR; } // ScribbleToStream // // ReadFromStream // // Likewise overriden to restore our state from a stream // HRESULT CEZrgb24::ReadFromStream(IStream *pStream) { HRESULT hr; READIN(m_effect); READIN(m_effectStartTime); READIN(m_effectTime); return NOERROR; } // ReadFromStream // // GetPages // // Returns the clsid's of the property pages we support // STDMETHODIMP CEZrgb24::GetPages(CAUUID *pPages) { pPages->cElems = 1; pPages->pElems = (GUID *) CoTaskMemAlloc(sizeof(GUID)); if (pPages->pElems == NULL) { return E_OUTOFMEMORY; } *(pPages->pElems) = CLSID_EZrgb24PropertyPage; return NOERROR; } // GetPages // // get_IPEffect // // Return the current effect selected // STDMETHODIMP CEZrgb24::get_IPEffect(int *IPEffect,REFTIME *start,REFTIME *length) { CAutoLock cAutolock(&m_EZrgb24Lock); CheckPointer(IPEffect,E_POINTER); CheckPointer(start,E_POINTER); CheckPointer(length,E_POINTER); *IPEffect = m_effect; *start = COARefTime(m_effectStartTime); *length = COARefTime(m_effectTime); return NOERROR; } // get_IPEffect // // put_IPEffect // // Set the required video effect // STDMETHODIMP CEZrgb24::put_IPEffect(int IPEffect,REFTIME start,REFTIME length) { CAutoLock cAutolock(&m_EZrgb24Lock); m_effect = IPEffect; m_effectStartTime = COARefTime(start); m_effectTime = COARefTime(length); SetDirty(TRUE); return NOERROR; } // put_IPEffect