//==========================================================================;
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
// KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
// PURPOSE.
//
// Copyright (c) 1992 - 1997 Microsoft Corporation. All Rights Reserved.
//
//--------------------------------------------------------------------------;
//
//
// 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
// ezrgb24.reg What goes in the registry to make us work
// 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 <windows.h>
#include <streams.h>
#include <initguid.h>
#if (1100 > _MSC_VER)
#include <olectlid.h>
#else
#include <olectl.h>
#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