DR20190930
编码实现
Filter的编码实现包括Filter的注册信息、Filter上的框架函数实现、逻辑控制类实
现、自定义接口实现、属性页实现、产权保护等。
1.Filter注册信息
a.在...\Microsoft Visual Studio\Common\Tools下运行guidgen.exe获取一个新的
CLSID。
形式如下:
// {683474FE-DF11-4281-97C5-97C7EAA5483B}
DEFINE_GUID(<<name>>,
0x683474fe, 0xdf11, 0x4281, 0x97, 0xc5, 0x97, 0xc7, 0xea, 0xa5, 0x48, 0x3b);
我们再把其中的<<name>>替换成自己设置的名字,代码如下:
// {683474FE-DF11-4281-97C5-97C7EAA5483B}
DEFINE_GUID(CLSID_HQTitleOverlay,
0x683474fe, 0xdf11, 0x4281, 0x97, 0xc5, 0x97, 0xc7, 0xea, 0xa5, 0x48, 0x3b);
注意:在上述GUID字符串之前,需要加入#include<initguid.h>,将其转化为GUID常量。
b.填写Filter的注册信息,代码如下:
//setup data
const AMOVIESETUP_MEDIATYPE sudPinTypes=
{
&MEDIATYPE_NULL, //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 sudFilter=
{
&CLSID_HQTitleOverlay, //Filter CLSID
L"HQ Title Overlay Std.", //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[]=
{
{
L"HQ Title Overlay Std.",
&CLSID_HQTitleOverlay,
CFilterTitleOverlay::CreateInstance,
NULL,
&sudFilter
},
{
L"HQ Title Overlay Property Page",
&CLSID_HQTitleOverlayProp,
CTitleOverlayProp::CreateInstance
}
};
int g_cTemplates=sizeof(g_Templates)/sizeof(g_Templates[0]);
我们看到,我们的Filter注册名字为HQ Title Overlay Std.,有一个输入Pin和一个输
出Pin,Filter的Merit值为MERIT_DO_NOT_USE,Filter还实现了一个属性页。
Filter的编码实现包括Filter的注册信息、Filter上的框架函数实现、逻辑控制类实
现、自定义接口实现、属性页实现、产权保护等。
1.Filter注册信息
a.在...\Microsoft Visual Studio\Common\Tools下运行guidgen.exe获取一个新的
CLSID。
形式如下:
// {683474FE-DF11-4281-97C5-97C7EAA5483B}
DEFINE_GUID(<<name>>,
0x683474fe, 0xdf11, 0x4281, 0x97, 0xc5, 0x97, 0xc7, 0xea, 0xa5, 0x48, 0x3b);
我们再把其中的<<name>>替换成自己设置的名字,代码如下:
// {683474FE-DF11-4281-97C5-97C7EAA5483B}
DEFINE_GUID(CLSID_HQTitleOverlay,
0x683474fe, 0xdf11, 0x4281, 0x97, 0xc5, 0x97, 0xc7, 0xea, 0xa5, 0x48, 0x3b);
注意:在上述GUID字符串之前,需要加入#include<initguid.h>,将其转化为GUID常量。
b.填写Filter的注册信息,代码如下:
//setup data
const AMOVIESETUP_MEDIATYPE sudPinTypes=
{
&MEDIATYPE_NULL, //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 sudFilter=
{
&CLSID_HQTitleOverlay, //Filter CLSID
L"HQ Title Overlay Std.", //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[]=
{
{
L"HQ Title Overlay Std.",
&CLSID_HQTitleOverlay,
CFilterTitleOverlay::CreateInstance,
NULL,
&sudFilter
},
{
L"HQ Title Overlay Property Page",
&CLSID_HQTitleOverlayProp,
CTitleOverlayProp::CreateInstance
}
};
int g_cTemplates=sizeof(g_Templates)/sizeof(g_Templates[0]);
我们看到,我们的Filter注册名字为HQ Title Overlay Std.,有一个输入Pin和一个输
出Pin,Filter的Merit值为MERIT_DO_NOT_USE,Filter还实现了一个属性页。
c.最后,我们要来定义两个导出函数:DllRegisterServer和DllUnregisterServer,代码如
下:
STDAPI DllRegisterServer()
{
return AMovieDllRegisterServer2(TRUE);
}
STDAPI DllUnregisterServer()
{
return AMovieDllRegisterServer2(FALSE);
}
d.让Filter项目编译成功后自动注册。
打开VC的菜单,选择Project|Setting|Custom Build,在这一页的各项中输入如下代码即可:
Description:Register filter
Commands:regsvr32 /s /c "$(TargetPath)"
echo regsvr32 exec. time > "$(TargetDir)\$(TargetName).trg"
Outputs:$(TargetDir)\$(TargetName).trg
2.框架函数的实现
a.CreateInstance函数可以创建Filter对象实例
CUnknown *WINAPI CFilterTitleOverlay::CreateInstance(LPUNKNOWN punk,HRESULT *phr)
{
CFilterTitleOverlay *pNewObject=new
CFilterTitleOverlay(NAME("TitleOverlay"),punk,phr);
if(pNewObject==NULL)
{
*phr=E_OUTOFMEMORY;
}
return pNewObject;
}
b.CheckInputType函数可以完成输入Pin上的媒体类型检查,代码如下:
//我们需要支持RGB32,RGB24,RGB565,RGB555等格式
HRESULT CFilterTitleOverlay::CheckInputType(const CMediaType* mtIn)
{
//Dynamic format change will never be allowed!
if(IsStopped()&&*mtIn->Type()==MEDIATYPE_Video)
{
if(*mtIn->Subtype()==MEDIASUBTYPE_RGB32||
*mtIn->Subtype()==MEDIASUBTYPE_RGB24||
*mtIn->Subtype()==MEDIASUBTYPE_RGB555||
*mtIn->Subtype()==MEDIASUBTYPE_RGB565)
{
return NOERROR;
}
}
return E_INVALIDARG;
}
c.Transform函数可以完成字符叠加函数的调用:
HRESULT CFilterTitleOverlay::Transform(IMediaSample *pSample)
{
//如果我们不能从输入Pin的媒体类型中获得帧率参数信息,
//则根据第一个Sample上的时间戳来计算
if(mNeedEstimateFrameRate)
{
mNeedEstimateFrameRate=FALSE;
REFERENCE_TIME startTime=0;
REFERENCE_TIME endTime=0;
double estimated=25;
if(SUCCEEDED(pSample->GetTime(&startTime,&endTime)))
{
estimated=1.0*UNITS/(endTime-startTime);
}
mOverlayController->SetEstimatedFrameRate(estimated);
}
if(mOverlayType!=OT_NONE)
{
PBYTE pData=NULL;
pSample->GetPointer(&pData);
mOverlayController->DoTitleOverlay(pData);
}
return NOERROR;
}
从上面可以看出,mOverlayController->DoTitleOverlay(pData)是真正实现字符叠加的函数
调用。
d.NonDelegatingQueryInterface函数可以“暴露”Filter支持的接口,代码如下:
//Basic COM-used here to reveal our own interfaces
STDMETHODIMP CFilterTitleOverlay::NonDelegatingQueryInterface(REFIID
riid,void **ppv)
{
CheckPointer(ppv,E_POINTER);
if(riid==IID_ISpecifyPropertyPages) //属性页接口
{
return GetInterface((ISpecifyPropertyPages *)this,ppv);
}
else if(riid==IID_ITitleOverlay) //自定义接口
{
return GetInterface((ITitleOverlay *)this,ppv);
}
else
{
return CTransInPlaceFilter::NonDelegatingQueryInterface(riid,ppv);
}
}
需要在CFilterTitleOverlay类定义中加入DECLARE_IUNKNOWN声明。
e.CompleteConnect函数,可以在输入Pin成功连接后取得媒体类型描述,代码如下:
HRESULT CFilterTitleOverlay::CompleteConnect(PIN_DIRETION direction,IPin *pReceivePin)
{
HRESULT hr=CTransInPlaceFilter::CompleteConnect(direction,pReceivePin);
if(SUCCEEDED(hr)&&direction==PINDIR_INPUT)
{
hr=SetInputVideoInfoToController();
}
return hr;
}
其中,SetInputVideoInfoToController函数将输入数据的格式设置给应用逻辑控制对象,该
函数实现代码如下:
HRESULT CFilterTitleOverlay::SetInputVideoInfoToController(void)
{
if(mOverlayController&&m_pInput&&m_pInput->IsConnected())
{
CMediaType mt=m_pInput->CurrentMediaType();
if(mt.formattype!=FORMAT_VideoInfo)
{
return E_FAIL;
}
RGB_FORMAT colorSpace=FT_NONE;
if(mt.subtype==MEDIASUBTYPE_RGB32) //Determine RGB format
{
colorSpace=FT_RGB32;
}
else if(mt.subtype==MEDIASUBTYPE_RGB24)
{
colorSpace=FT_RGB24;
}
else if(mt.subtype==MEDIASUBTYPE_RGB555)
{
colorSpace=FT_RGB555;
}
else if(mt.subtype==MEDIASUBTYPE_RGB565)
{
colorSpace=FT_RGB565;
}
else if(mt.subtype==MEDIASUBTYPE_RGB8)
{
colorSpace=FT_RGB8;
}
mOverlayController->SetInputColorSpace(colorSpace);
VIDEOINFOHEADER *pHeader=(VIDEOINFOHEADER *)mt.pbFormat;
mNeedEstimateFrameRate=pHeader->AvgTimePerFrame>0?FALSE:TRUE;
mOverlayController->SetInputVideoInfo(pHeader);
return NOERROR;
}
return E_FAIL;
}
f.StartStreaming和StopStreaming函数,可以分别调用应用逻辑控制对象的相应函
数,进行初始化和反初始化,代码如下:
HRESULT CFilterTitleOverlay::StartStreaming()
{
BOOL pass=mOverlayController->StartTitleOverlay();
return pass?S_OK:E_FAIL;
}
HRESULT CFilterTitleOverlay::StopStreaming()
{
mOverlayController->StopTitleOverlay();
return NOERROR;
}
下:
STDAPI DllRegisterServer()
{
return AMovieDllRegisterServer2(TRUE);
}
STDAPI DllUnregisterServer()
{
return AMovieDllRegisterServer2(FALSE);
}
d.让Filter项目编译成功后自动注册。
打开VC的菜单,选择Project|Setting|Custom Build,在这一页的各项中输入如下代码即可:
Description:Register filter
Commands:regsvr32 /s /c "$(TargetPath)"
echo regsvr32 exec. time > "$(TargetDir)\$(TargetName).trg"
Outputs:$(TargetDir)\$(TargetName).trg
2.框架函数的实现
a.CreateInstance函数可以创建Filter对象实例
CUnknown *WINAPI CFilterTitleOverlay::CreateInstance(LPUNKNOWN punk,HRESULT *phr)
{
CFilterTitleOverlay *pNewObject=new
CFilterTitleOverlay(NAME("TitleOverlay"),punk,phr);
if(pNewObject==NULL)
{
*phr=E_OUTOFMEMORY;
}
return pNewObject;
}
b.CheckInputType函数可以完成输入Pin上的媒体类型检查,代码如下:
//我们需要支持RGB32,RGB24,RGB565,RGB555等格式
HRESULT CFilterTitleOverlay::CheckInputType(const CMediaType* mtIn)
{
//Dynamic format change will never be allowed!
if(IsStopped()&&*mtIn->Type()==MEDIATYPE_Video)
{
if(*mtIn->Subtype()==MEDIASUBTYPE_RGB32||
*mtIn->Subtype()==MEDIASUBTYPE_RGB24||
*mtIn->Subtype()==MEDIASUBTYPE_RGB555||
*mtIn->Subtype()==MEDIASUBTYPE_RGB565)
{
return NOERROR;
}
}
return E_INVALIDARG;
}
c.Transform函数可以完成字符叠加函数的调用:
HRESULT CFilterTitleOverlay::Transform(IMediaSample *pSample)
{
//如果我们不能从输入Pin的媒体类型中获得帧率参数信息,
//则根据第一个Sample上的时间戳来计算
if(mNeedEstimateFrameRate)
{
mNeedEstimateFrameRate=FALSE;
REFERENCE_TIME startTime=0;
REFERENCE_TIME endTime=0;
double estimated=25;
if(SUCCEEDED(pSample->GetTime(&startTime,&endTime)))
{
estimated=1.0*UNITS/(endTime-startTime);
}
mOverlayController->SetEstimatedFrameRate(estimated);
}
if(mOverlayType!=OT_NONE)
{
PBYTE pData=NULL;
pSample->GetPointer(&pData);
mOverlayController->DoTitleOverlay(pData);
}
return NOERROR;
}
从上面可以看出,mOverlayController->DoTitleOverlay(pData)是真正实现字符叠加的函数
调用。
d.NonDelegatingQueryInterface函数可以“暴露”Filter支持的接口,代码如下:
//Basic COM-used here to reveal our own interfaces
STDMETHODIMP CFilterTitleOverlay::NonDelegatingQueryInterface(REFIID
riid,void **ppv)
{
CheckPointer(ppv,E_POINTER);
if(riid==IID_ISpecifyPropertyPages) //属性页接口
{
return GetInterface((ISpecifyPropertyPages *)this,ppv);
}
else if(riid==IID_ITitleOverlay) //自定义接口
{
return GetInterface((ITitleOverlay *)this,ppv);
}
else
{
return CTransInPlaceFilter::NonDelegatingQueryInterface(riid,ppv);
}
}
需要在CFilterTitleOverlay类定义中加入DECLARE_IUNKNOWN声明。
e.CompleteConnect函数,可以在输入Pin成功连接后取得媒体类型描述,代码如下:
HRESULT CFilterTitleOverlay::CompleteConnect(PIN_DIRETION direction,IPin *pReceivePin)
{
HRESULT hr=CTransInPlaceFilter::CompleteConnect(direction,pReceivePin);
if(SUCCEEDED(hr)&&direction==PINDIR_INPUT)
{
hr=SetInputVideoInfoToController();
}
return hr;
}
其中,SetInputVideoInfoToController函数将输入数据的格式设置给应用逻辑控制对象,该
函数实现代码如下:
HRESULT CFilterTitleOverlay::SetInputVideoInfoToController(void)
{
if(mOverlayController&&m_pInput&&m_pInput->IsConnected())
{
CMediaType mt=m_pInput->CurrentMediaType();
if(mt.formattype!=FORMAT_VideoInfo)
{
return E_FAIL;
}
RGB_FORMAT colorSpace=FT_NONE;
if(mt.subtype==MEDIASUBTYPE_RGB32) //Determine RGB format
{
colorSpace=FT_RGB32;
}
else if(mt.subtype==MEDIASUBTYPE_RGB24)
{
colorSpace=FT_RGB24;
}
else if(mt.subtype==MEDIASUBTYPE_RGB555)
{
colorSpace=FT_RGB555;
}
else if(mt.subtype==MEDIASUBTYPE_RGB565)
{
colorSpace=FT_RGB565;
}
else if(mt.subtype==MEDIASUBTYPE_RGB8)
{
colorSpace=FT_RGB8;
}
mOverlayController->SetInputColorSpace(colorSpace);
VIDEOINFOHEADER *pHeader=(VIDEOINFOHEADER *)mt.pbFormat;
mNeedEstimateFrameRate=pHeader->AvgTimePerFrame>0?FALSE:TRUE;
mOverlayController->SetInputVideoInfo(pHeader);
return NOERROR;
}
return E_FAIL;
}
f.StartStreaming和StopStreaming函数,可以分别调用应用逻辑控制对象的相应函
数,进行初始化和反初始化,代码如下:
HRESULT CFilterTitleOverlay::StartStreaming()
{
BOOL pass=mOverlayController->StartTitleOverlay();
return pass?S_OK:E_FAIL;
}
HRESULT CFilterTitleOverlay::StopStreaming()
{
mOverlayController->StopTitleOverlay();
return NOERROR;
}
3.逻辑控制类的实现
逻辑控制类才是字符叠加应该真正实现的地方。下面首先介绍字符叠加原理。
一般输入的每个视频Sample(非压缩格式)都带有一帧图像数据,字符叠加实际上就
是将指定位置的图像像素值替换为字符图像的像素值。那么,怎么替换效率更高一点呢?
一种直观的想法是,将图像帧选入GDI的DC中,再使用GDI函数TextOut和DrawText等直接在
图像帧上输出字符,然后再取出图像帧并返回给Filter框架。实际上,这种做法的效率不是
理想,要想做到实时叠加难度很大。
我们采用的一种方法是,在内存中首先建立一个二色位图,然后在这个位图上画出字符
内容。于是,我们就得到了字符内容的一块点阵信息:比如0表示背景,1表示前景字符。在
实际叠加的时候,我们将图像帧指定位置的像素与字符点阵的像素位对应,如果字符点阵的
像素位值为0,则保持图像帧对应的像素值不变;如果为1,则将图像帧对应的像素替换为用
户设置的字符颜色值。
创建字符内容的二色位图的代码如下:
BOOL COverlayController::CreateTitleDIBBits(void)
{
//创建一个与桌面兼容的内存DC
HDC hdc=CreateCompatibleDC(NULL);
if(hdc)
{
//创建字符位图
HBITMAP hbm=ActualCreateTitleDIB(hdc);
DeleteDC(hdc);
if(hbm)
{
DeleteObject(hbm);
return TRUE;
}
}
return FALSE;
}
HBITMAP COverlayController::ActualCreateTitleDIB(HDC inDC)
{
//创建字符位图使用的DIB信息
//创建的位图初始为全黑背景,而在有输出的区域为白底黑字
struct{
BITMAPINFOHEADER bmiHeader;
DWORD rgbEntries[2];
}bmi=
{
{
sizeof(BITMAPINFOHEADER),
0,
0,
1,
1,
BI_RGB,
0,
0,
0
},
{
0x00000000,
0xffffffff
}
};
//设置字体
CAutoFont autoFont;
if(mIsFontChanged)
{
autoFont.CreateFont(mTitleFont);
autoFont.SelectToDC(inDC);
}
//得到整个字符内容的宽和高,以决定我们将要创建的位图的大小
GetTextExtentPoint32(inDC,mTitle,lstrlen(mTitle),&mTitleSize);
//给子类一个机会改变将要创建的位图的大小
if(!ValidateTitleDIBSize())
{
return NULL;
}
//设置将要创建的位图的大小
bmi.bmiHeader.biHeight=mTitleSize.cy;
bmi.bmiHeader.biWidth=mTitleSize.cx;
HBITMAP hbm=CreateDIBitmap(inDC,&bmi.bmiHeader,0,NULL,NULL,0);
BOOL pass=(hbm!=NULL);
if(pass)
{
//在位图上输出字符内容
HGDIOBJ hobj=SelectObject(inDC,hbm);
pass=ExtTextOut(inDC,0,0,ETO_OPAQUE|ETO_CLIPPED,NULL,
mTitle,lstrlen(mTitle),NULL);
SelectObject(inDC,hobj);
}
//获取位图的数据(相当于点阵信息)
if(pass)
{
ReleaseTitleDIB();
//Attention:To get bitmap data from the DIB object,
//the scan line must be a multiple of 4(DWORD)!
//If the actual bitmap data is not exactly fit for DWORD,
//The rest of DWORD bits will be filled automatically.
//So we should expand to bytes and round up to a multiple of 4.
mDIBWidthInBytes=((mTitleSize.cx+31)>>3)& ~3;
//上面4字节对齐公式的解释,>>3等于除于8就变成字节对齐,然后再
//和~3(即111111111111....11100)与,通过0x0100*n能得到最后两
//位为00的4的整数倍的数;最后两位为00的4的整数倍的数可以由0x0100*n
//计算得到,证明这个充分必要条件
mTitleDIBBits=new BYTE[mDIBWidthInBytes*mTitleSize.cy];
memset(mTitleDIBBits,0,mDIBWidthInBytes *mTitleSize.cy);
LONG lLines=GetDIBits(inDC,hbm,0,mTitleSize.cy,(PVOID)mTitleDIBBits,
(BITMAPINFO *)&bmi,DIB_RGB_COLORS);
pass=(lLines!=0);
}
if(!pass&&hbm)
{
DeleteObject(hbm);
hbm=NULL;
}
return hbm;
}
在实际的字符叠加过程中,还有一个问题,就是在设计逻辑控制类的时候,我们事先并不能
知道输入的图像到底是RGB的哪种格式。这需要在Filter的输入Pin连接完成后设置进来。然而,
不同的RGB格式每个像素使用的字节数不一样,像素值的替换方式也就不一样。我们希望用一种
统一的方式来控制。于是,我们对像素进行了抽象,设计了一个像素操作类结构。
class CBasePixel
{
protected:
unsigned char m_TargetR;//字符颜色的RGB值
unsigned char m_TargetG;
unsigned char m_TargetB;
unsigned char m_PixelSize;//每个像素使用的字节数
public:
CBasePixel();
~CBasePixel();
void SetTargetColor(unsigned char inR,unsigned char inG,unsigned char inB);
void SetPixelSize(int inSize);
int GetPixelSize(void){return m_PixelSize;}
//直接替换的叠加方式
virtual void ConvertByCover(unsigned char *inPixel);
//使用反色叠加,使前景的字符在背景视频图像上更突出
virtual void ConverByReverse(unsigned char *inPixel);
//指向下一个像素
unsigned char *NextPixel(unsigned char *inCurrent);
//指向下N个像素
unsigned char *NextNPixel(unsigned char *inCurrent,int inCount);
protected:
virtual void SideEffectColorChanged(void);
};
CBasePixel的子类分别实现各自的ConvertByCover函数,用于实际像素值的替换。
我们看到控制类COverlayController定义了一个CBasePixel类型的成员对象指针。根据输
入图像的具体格式,我们生成不同的CBasePixel子类对象实例。于是,我们可以统一调用
CBasePixel::ConvertByCover或CBasePixel::ConvertByReverse来完成像素值的替换。
我们再回到控制类的实现问题上。我们看到,控制类上定义了很多成员函数,用于参
数的设置。另外定义了3个重要的公共函数:
virtual BOOL StartTitleOverlay(void);//Filter进入运行状态时调用,以便初始化
virtual BOOL StopTitleOverlay(void);//Filter进入停止状态时调用,以便反初始化
BOOL DoTitleOverlay(PBYTE inImage);//真正的字符叠加函数
DoTitleOverlay的实现代码如下:
BOOL COverlayController::DoTitleOverlay(PBYTE inImage)
{
if(!mCanDoOverlay)
{
return FALSE;
}
BOOL pass=BeforeActualOverlay();//开始叠加前进必要的准备
if(pass)
{
pass=ActualOverlay(inImage);//真正的叠加
}
if(pass)
{
pass=AfterActualOverlay();//叠加后的“善后”工作
}
return pass;
}
其中,BeforeActualOverlay、ActualOverlay、AfterActualOverlay都是虚函数(子类都
可以重新实现)。之所以如此编写,是因为COverlayController是一个基类,更多的职责在于
设计应用框架,而留出虚函数让子类根据具体应用去定制。
ActualOverlay函数的实现如下:
BOOL COverlayController::ActualOverlay(PBYTE inImage)
{
//Now copy the data from the DIB section(which is usually bottom-up)
//but first check if it's too big
if(mImageWidth>mStartPos.x&&mImageHeight>mStartPos.y&&
mTitleSize.cx>0&&mTitleSize.cy>0)
{
long actualOverlayWidth=min((mImageWidth-mStartPos.x),mTitleSize.cx);
long actualOverlayHeight=min((mImageHeight-mStartPos.y),mTitleSize.cy);
//Image may be bottom-up, may be top-down
//Anyway retrieve the pointer which point to the top line
PBYTE pTopLine=NULL;
long strideInBytes=0;
if(mIsBottomUpImage)
{
strideInBytes=-mImageWidthInBytes;
pTopLine=inImage+mImageWidthInBytes*(mImage-Height-1);
}
else
{
strideInBytes=mImageWidthInBytes;
pTopLine=inImage;
}
PBYTE pStartPos=pTopLine+mStartPos.y*strideInBytes;
pStartPos+=(mStartPos.x *mImageBitCount/8);
for(DWORD dwY=0;dwY<(DWORD)actualOverlayHeight;dwY++)
{
PBYTE pbTitle=mTitleDIBBits;
pbTitle+=(mDIBWidthInBytes*((DWORD)mTitleSize.cy-dwY-1));
for(DWORD dwX=0;dwX<(DWORD)actualOverlayWidth;dwX++)
{
//dwX&7,value from 0-7
//0x80>>(dwX&7),value from 10000000 to 00000001
//dwX>>3,value add one every eight
//If the source bit is 0,the background.If 1,draw the text.
if(!((0x80>>(deX&7))&pbTitle[dwX>>3]))
{
PBYTE pbPixel=mPixelConverter->NextNPixel(pStartPos,dwX);
if(mIsOverlayByCover)
{
mPixelConverter->ConvertByCover(pbPixel);
}
else
{
mPixelConverter->ConvertByReverse(pbPixel);
}
}
}
pStartPos+=strideInBytes;
}
return TRUE;
}
return FALSE;
}
上述函数中,首先判断叠加位置的有效性;然后计算得到一个指向图像帧第一行数据
的指针。图像一般有两种扫描方式:从下往上(Bottom-Up)和从上往下(Top-Down)。前
者多用于GDI环境,图像数据存储时,最后一行的数据放在最前面,然后依次放倒数第2行的
数据。这时候计算指向图像第1行数据的指针,我们必须从图像数据开始地址计算一个偏移
量;后者多用于DirectDraw环境,是正常的图像数据存储格式。图像的扫描方式用
BITMAPINFOHEADER数据结构的biHeight成员值的正负来表示:正数表示从下往上,负数表示
从上往下。mIsBottomUpImage的取值如下:
mIsBottomUpImage=inInfoHeader->bmiHeader.biHeight>0?TRUE:FALSE;
再下来就是一个两层循环,完成了字符内容像素值在图像帧中对应位置的替换。
关于逻辑控制类的扩展问题,比如要叠加系统时间,我们就可以从COverlayController
中派生一个类CSysTimeOverlayController。因为系统时间是一直在变的,所以这个控制类的
特点是,每次叠加前都必须重新生成字符内容的点阵信息。我们主要通过重新实现
BeforeActualOverlay函数来完成,代码如下:
BOOL CSysTimeOverlayController::BeforeActualOverlay(void)
{
//更新当前系统时间
SYSTEMTIME systemTime,localTime;
GetSystemTime(&systemTime);//This is Coordinated Universal Time(UTC)
SystemTimeToTzSpecificLocalTime(NULL,&systemTime,&localTime);
sprintf(mTitle,"%4d-%02d-%02d(%02d:%02d:%02d)",localTime.wYear,localTime.wMonth,
localTime.wDay,localTime.wHour,localTime.wMinute,localTime.wSecond);
//重新创建字符点阵信息
BOOL pass=CreateTitleDIBBits();
return pass;
}
再比如我们要扩展滚动字幕叠加控制,同样也是从COverlayController中派生一个类
CScrollController。因为滚动字幕的叠加坐标以及字符内容的显示区域需要实时计算获得,这个
控制类的主要实现是,重写BeforeActualOverlay函数以便每次叠加前完成上述计算工作,并且重
写ActualOverlay函数,完成在动态指定的坐标上叠加有效区域的字符内容。代码如下:
BOOL CScrollController::BeforeActualOverlay(void)
{
BOOL pass=COverlayController::BeforeActualOverlay();
if(pass)
{
//Calculate for the next progress
long actualProgress=mOverlayCounter-mOverlayStartTime;
mStartPos.x=long(mImageWidth-1-mScrollStride*actualProgress);
if(mStartPos.x<0)
{
//Moving out of the left side
mValidTitleRect.left=-mStartPos.x;
if(mValidTitleRect.left>=mTitleSize.cx)
{
mValidTitleRect.left=mTitleSize.cx-1;
}
mStartPos.x=0;
if(mTitleSize.cx-mValidTitleRect.left<=mImageWidth)
{
mValidTitleRect.right=mTitleSize.cx-1;
}
else
{
mValidTitleRect.right=mValidTitleRect.left+mImageWidth;
}
}
else
{
//Moving in the image-width range
mValidTitleRect.left=0;
long currentLength=mImageWidth-mStartPos.x;
if(currentLength>=mTitleSize.cx)
{
mValidTitleRect.right=mTitleSize.cx-1;
}
else
{
mValidTitleRect.right=currentLength;
}
}
pass=(mValidTitleRect.right>mValidTitleRect.left);
}
return pass;
}
//mStartPos为每次叠加前计算所得的开始坐标
//mValidTitleRect为有效的字符区域
BOOL CScrollController::ActualOverlay(PBYTE inImage)
{
if(mImageHeight>mTitleSize.cy&&mTitleSize.cx>0&&
mTitleSize.cy>0)
{
//Image may be bottom-up, may be top-down
//Anyway retrieve the pointer which point to the top line
PBYTE pTopLine=NULL;
long strideInBytes=0;
if(mIsBottomUpImage)
{
strideInBytes=-mImageWidthInBytes;
pTopLine=inImage+mImageWidthInBytes*(mImage-Height-1);
}
else
{
strideInBytes=mImageWidthInBytes;
pTopLine=inImage;
}
PBYTE pStartPos=pTopLine+mStartPos.y*strideInBytes;
pStartPos+=(mStartPos.x*mImageBitCount/8);
for(DWORD dwY=0;dwY<(DWORD)mTitleSize.cy;dwY++)
{
PBYTE pbTitle=mTitleDIBBits;
pbTitle+=(mDIBWidthInBytes*((DWORD)mTitleSize.cy-dwY-1));
//Point to the valid start position of title DIB
pbTitle+=(mValidTitleRect.left>>3);
long startLeft=mValidTitleRect.left%8;
long endRight=startLeft+mValidTitleRect.right;
endRight-=mValidTitleRect.left;
for(long dwX=startLefr;dwX<endRight;dwX++)
{
if(!((0x80>>(dwX&7))&pbTitle[dwX>>3]))
{
PBYTE pbPixel=mPixelConverter->NextNPixel(pStartPos,dwX-startLeft);
if(mIsOverlayByCover)
{
mPixelConverter->ConvertByCover(pbPixel);
}
else
{
mPixelConverter->ConvertByReverse(pbPixel);
}
}
}
pStartPos+=strideInBytes;
}
}
return TRUE;
}
4.自定义接口的实现
自定义接口的实现其实很简单。首先定义接口方法,代码如下:
//ITitleOverlay.h
//Desc:DirectShow sample code - custom interface
#ifndef __H_ITitleOverlay__
#define __H_ITitleOverlay__
#ifdef __cplusplus
extern "C"{
#endif
//ITitleOverlay's GUID
//{5E5B3386-6F9F-4a47-AC8B-7A302138D7FF}
DEFINE_GUID(IID_ITitleOverlay,0x5e5b3386,0x6f9f,0x4a47,0xac,0x8b,
0x7a,0x30,0x21,0x38,0xd7,0xff);
//ITitleOverlay
DECLARE_INTERFACE_(ITitleOverlay,IUnknown)
{
//设置Filter进行叠加的类型,如果需要改变类型,必须第1个设置这个函数,
//调用这个函数成功后,才能调用其他的函数进行参数设置
//可以设置的叠加类型参见枚举类型OVERLAY_TYPE的定义
STDMETHOD(put_TitleOverlayType)(THIS_long inOverlayType)PURE;
STDMETHOD(get_TitleOverlayType)(THIS_long *outOverlayType)PURE;
//设置像素转换的方式(可选),默认使用直接替换方式
//当字符颜色与视频背景颜色接近时,使用翻转叠加方式比较合适
STDMETHOD(put_TitleOverlayStyle)(THIS_int inUsingCover)PURE;
STDMETHOD(get_TitleOverlayStyle)(THIS_int *outUsingCover)PURE;
//设置欲叠加字符的内容
STDMETHOD(put_Title)(THIS_const char* inTitle,int inLength)PURE;
STDMETHOD(get_Title)(THIS_char *outBuffer,int *outLength)PURE;
//设置欲叠加字符的颜色,分别为R,G,B分量值
STDMETHOD(put_TitleColor)(THIS_BYTE inR,BYTE inG,BYTE inB)PURE;
//设置欲叠加字符的开始坐标
STDMETHOD(put_TitleStartPosition)(THIS_POINT inStartPos)PURE;
STDMETHOD(get_TitleStartPosition)(THIS_POINT *outStartPos)PURE;
//设置欲叠加字符的字体,参数采用Windows GDI的LOGFONT数据结构
STDMETHOD(put_TitleFont)(THIS_LOGFONT inFont)PURE;
STDMETHOD(get_TitleFont)(THIS_LOGFONT *outFont)PURE;
//设置欲叠加字符的生存周期,一个参数是开始时间,一个参数是结束时间,以s为单位
//如果inEnd值为-1,表示一直迭加到视频结束
STDMETHOD(put_TitleDuration)(THIS_double inStart,double inEnd)PURE;
STDMETHOD(get_TitleDuration)(THIS_double *outStart,double *outEnd)PURE;
};
#ifdef __cplusplus
}
#endif
#endif//__H_ITitleOverlay__
然后将Filter类从ITitleOverlay派生,并声明ITitleOverlay的所有接口方法,代码如下:
//从ITitleOverlay中继承Filter类
class CFilterTitleOverlay:public CTransInPlaceFilter,public ITitleOverlay
{
//...
//---ITitleOverlay methods---
STDMETHODIMP put_TitleOverlayType(long inOverlayType);
STDMETHODIMP get_TitleOverlayType(long *outOverlayTyoe);
STDMETHODIMP put_TitleOverlayStyle(int inUsingCover);
STDMETHODIMP get_TitleOverlayStyle(int *outUsingCover);
STDMETHODIMP put_Title(const char*inTitle,int inLength);
STDMETHODIMP get_Title(char* outBuffer,int *outLength);
STDMETHODIMP put_TitleColor(BYTE inR,BYTE inG,BYTE inB);
STDMETHODIMP get_TitleColor(BYTE *outR,BYTE *outG,BYTE *outB);
STDMETHODIMP put_TitleStartPosition(POINT inStartPos);
STDMETHODIMP get_TitleStartPosition(POINT *outStartPos);
STDMETHODIMP put_TitleFont(LOGFONT inFont);
STDMETHODIMP get_TitleFont(LOGFONT *outFont);
STDMETHODIMP put_TitleDuration(double inStart,double inEnd);
STDMETHODIMP get_TitleDuration(double *outStart,double *outEnd);
}
在Filter类的实现文件中实现这些接口方法(其实大都是调用应用逻辑控制类的相应函数),
代码如下:
//---ITitleOverlay methods---
STDMETHODIMP CFilterTitleOverlay::put_TitleOverlayType(long inOverlayType)
{
CAutoLock lockit(&mITitleOverlaySync);
//When filter graph is active,change overlay type is not allowed!
if(m_State!=State Stopped)
{
return E_UNEXPECTED;
}
if(mOverlayType!=(OVERLAY_TYPE)inOverlayType)
{
mOverlayType=(OVERLAY_TYPE)inOverlayType;
SideEffectOverlayTypeChanged();
}
return NOERROR;
}
//当字符叠加的类型改变时,需要生成新的应用逻辑控制类对象实例,
//并且在新生成的控制对象上设置参数信息
void CFilterTitleOverlay::SideEffectOverlayTypeChanged(void)
{
ReleaseOverlayController();
switch(mOverlayType)
{
case OT_SYSTIME:
mOverlayController=new CSysTimeOverlayController();
break;
case OT_SCROLL_TOP:
mOverlayController=new CScrollController();
((CScrollController*)mOverlayController)->SetScrollBottomOrTop(FALSE);
break;
case OT_SCROLL_BOTTOM:
mOverlayController=new CScrollController();
((CScrollController*)mOverlayController)->SetScrollBottonOrTop(TRUE);
break;
case OT_NONE:
case OT_STATIC:
default:
mOverlayController=new COverlayController();
}
//Make sure to set input video info to the new controller
SetInputVideoInfoToController();
}
STDMETHODIMP CFilterTitleOverlay::get_TitleOverlayType(long *outOverlayType)
{
CAutoLock lockit(&mITitleOverlaySync);
*outOverlayType=mOverlayType;
return NOERROR;
}
STDMETHODIMP CFilterTitleOverlay::put_TitleOverlayStyle(int inUsingCover)
{
CAutoLock lockit(&mITitleOverlaySync);
mOverlayController->SetOverlayStyle(inUsingCover);
return NOERROR;
}
STDMETHODIMP CFilterTitleOverlay::get_TitleOverlayStyle(inr *outUsingCover)
{
CAutoLock lockit(&mITitleOverlaySync);
mOverlayController->GetOverlayStyle(outUsingCover);
return NOERROR;
}
STDMETHODIMP CFilterTitleOverlay::put_Title(const char*inTitle,int inLength)
{
CAutoLock lockit(&mITitleOverlaySync);
//When filter graph is active,change title text is not allowed!
if(m_State!=State_Stopped)
{
return E_UNEXPECTED;
}
mOverlayController->SetTitle(inTitle,inLength);
return NOERROR;
}
STDMETHODIMP CFilterTitleOverlay::get_Title(char *outBuffer,int *outLength)
{
CAutoLock lockit(&mITitleOverlaySync);
*outLength=mOverlayController->GetTitle(outBuffer);
return NOERROR;
}
STDMETHODIMP CFilterTitleOverlay::put_TitleColor(BYTE inR,BYTE inG,BYTE inB)
{
CAutoLock lockit(&mITitleOverlaySync);
mOverlayController->SetTitleColor(inR,inG,inB);
return NOERROR;
}
STDMETHODIMP CFilterTitleOverlay::get_TitleColor(BYTE *outR,BYTE *outG,BYTE *outB)
{
CAutoLock lockit(&mITitleOverlaySync);
mOverlayController->GetTitleColor(outR,outG,outB);
return NOERROR;
}
STDMETHODIMP CFilterTitleOverlay::put_TitleStartPosition(POINT inStartPos)
{
CAutoLock lockit(&mITitleOverlaySync);
mOverlayController->SetTitleStartPosition(inStartPos);
return NOERROR;
}
STDMETHODIMP CFilterTitleOverlay::get_TitleStartPosition(POINT *outStartPos)
{
CAutoLock lockit(&mITitleOverlaySync);
mOverlayController->GetTitleStartPosition(outStartPos);
return NOERROR;
}
STDMETHODIMP CFilterTitleOverlay::put_TitleFont(LOGFONT inFont)
{
CAutoLock lockit(&mITitleOverlaySync);
mOverlayController->SetTitleFont(outFont);
return NOERROR;
}
STDMETHODIMP CFilterTitleOverlay::put_TitleDuration(double inStart,double inEnd)
{
CAutoLock lockit(&mITitleOverlaySync);
mOverlayController->SetTitleDuration(inStart,inEnd);
return NOERROR;
}
STDMETHODIMP CFilterTitleOverlay::get_TitleDuration(double *outStart,double *outEnd)
{
CAutoLock lockit(&mITitleOverlaySync);
mOverlayController->GetTitleDuration(outStart,outEnd);
return NOERROR;
}
最后,在NonDelegatingQueryInterface函数中“暴露”我们的ITitleOverlay接口,代码如下:
STDMETHODIMP CFilterTitleOverlay::NonDelegatingQueryInterface(REFIID riid,void **ppv)
{
checkPointer(ppv,E_POINTER);
if(riid==IID_ITitleOverlay)
{
return GetInterface((ITitleOverlay*)this,ppv);
}
else
{
return CTransInPlaceFilter::NonDelegatingQueryInterface(riid,ppv);
}
}
属性页的实现
属性页是用户与Filter交互的简单界面(一般在商业软件中不推荐将Filter的属性页直接显示给用户)。
Filter属性页更多地用于开发时候的接口调试。当然,Filter也可以不实现属性页。
在“字符叠加Filter”中,我们首先用VC的资源编辑器生成一个对话框,并编辑好对话框的内容。
然后从CBasePropertyPage类中派生一个子类CTitleOverlayProp,用于实现我们的属性页。在CTitleOverlayProp
类实现中,我们主要重写了如下函数:
//创建属性页COM对象实例
CUnknown *WINAPI CTitleOverlayProp::CreateInstance(LPUNKNOWN lpunk,HRESULT *phr)
{
CUnknown *punk=new CTitleOverlayProp(lpunk,phr);
if(punk==NULL)
{
*phr=E_OUTOFMEMORY;
}
return punk;
}
//获取ITitleOverlay接口
HRESULT CTitleOverlayProp::OnConnect(IUnknown *pUnknown)
{
HRESULT hr=pUnknown->QueryInterface(IID_ITitleOverlay,(void **)&mIOverlay);
if(FAILED(hr))
{
return E_NOINTERFACE;
}
ASSERT(mIOverlay);
return NOERROR;
}
//释放ITitleOverlay接口
HRESULT CTitleOverlayProp::OnDisconnect()
{
//Release of Interface
if(mIOverlay==NULL)
{
return E_UNEXPECTED;
}
mIOverlay->Release();
mIOverlay=NULL;
return NOERROR;
}
//通过ITitleOverlay接口取得Filter上的各参数,并在属性页界面上显示
HRESULT CTitleOverlayProp::OnActivate()
{
FillOverlayTypeComboBox();
ReflectOverlayType();
ReflectOverlayStyle();
ReflectTitle();
ReflectTitleStartPosition();
ReflectTitleDuration();
ReflectTitleColor();
ReflectTitleFont();
return NOERROR;
}
//将属性页界面上用户设置的各参数,通过ITitleOverlay接口设置给Filter
HRESULT CTitleOverlayProp::OnApplyChanges()
{
EnterOverlayType();//This must be first invoked!!
EnterOverlayStyle();
EnterTitle();
EnterTitleStartPosition();
EnterTitleDuration();
EnterTitleColor();
EnterTitleFont();
return NOERROR;
}
注意,上述OnActivate和OnApplyChanges函数实现中调用的各函数都是CtitleOverlay Prop
实现的私有成员函数,用于操作各个参数的读取和设置。代码如下:
void CTitleOverlayProp::ReflectTitle(void)
{
int titleLength=0;
mIOverlay->get_Title(NULL,&titleLength);//获取字符长度
if(titleLength>0)
{
char *szTitle=new char[titleLength];
mIOverlay->get_Title(szTitle,&titleLength);//获得字符内容
SetWindowText(m_hEditTitle,szTitle);
delete[] szTitle;
}
}
void CTitleOverlayProp::EnterTitle(void)
{
char szTitle[2000];
//设置字符内容
if(GetWindowText(m_hEditTitle,szTitle,2000))
{
mIOverlay->put_Title(szTitle,strlen(szTitle));
}
else
{
mIOverlay->put_Title("",0);
}
}
另外,我们还实现了OnReceiveMessage函数,用于处理属性页窗口的消息,代码如下:
BOOL CTitleOverlayProp::OnReceiveMessage(HWND hwnd,
UINT uMsg,WPARAM wParam,LPARAM lParam)
{
switch(uMsg)
{
case WM_INITDIALOG:
{
//得到各界面元素的窗口句柄
m_hOverlayType=GetDlgItem(hwnd,IDC_COMBO_OVERLAY_TYPE);
m_hEditTitle=GetDlgItem(hwnd,IDC_EDIT_TITLE);
m_hEditStartX=GetDlgItem(hwnd,IDC_EDIT_STARIX);
m_hEditStartY=GetDlgItem(hwnd,IDC_EDIT_STARTY);
m_hEditStartTime=GetDlgItem(hwnd,IDC_EDIT_STARTTIME);
m_hEditEndTime=GetDlgItem(hwnd,IDC_EDIT_ENDTIME);
m_hEditColorR=GetDlgItem(hwnd,IDC_EDIT_COLORR);
m_hEditColorG=GetDlgItem(hwnd,IDC_EDIT_COLORG);
m_hEditColorB=GetDlgItem(hwnd,IDC_EDIT_COLORB);
break;
}
case WM_COMMAND:
{
if(HIWORD(wParam)==BN_CLICKED)
{
switch(LOWORD(wParam))
{
//改变字体
case IDC_BUTTON_CHANGE_FONT:
OnButtonChangeFont();
break;
}
}
SetDirty();
break;
}
}
return CBasePropertyPage::OnReceiveMessage(hwnd,uMsg,wParam,lParam);
}
其中,OnButtonChangeFont函数实现为弹出Windows标准的字体选择框,代码如下:
void CTitleOverlayProp::OnButtonChangeFont(void)
{
//Update the RGB values
BYTE colorR=0,colorG=0,colorB=0;
char szColor[100];
GetWindowText(m_hEditColorR,szColor,100);
colorR=atoi(szColor);
GetWindowText(m_hEditColorG,szColor,100);
colorG=atoi(szColor);
GetWindowText(m_hEditColorB,szColor,100);
colorB=atoi(szColor);
mTitleColor=RGB(colorR,colorG,colorB);
CHOOSEFONT cf;
//Initialize CHOOSEFONT
ZeroMemory(&cf,sizeof(CHOOSEFONT));
cf.lStructSize=sizeof(CHOOSEFONT);
cf.hInstance=g_hInst;
cf.hwndOwner=m_hwnd;
cf.lpLogFont=&mTitleFont;
cf.rgbColors=mTitleColor;
cf.Flags=CF_SCREENFONTS|CF_EFFECTS|CF_INITTOLOGFONTSTRUCT;
//调用标准的选择字体的API函数
if(ChooseFont(&cf))
{
mIsFontChanged=TRUE;
//Update the text color
mTitleColor=cf.rgbColors;
ReflectTitleColor(GetRValue(mTitleColor),GetGValue(mTitleColor),
GetBValude(mTitleColor));
}
}
最后在Filter上实现ISpecifyPropertyPages的接口方法GetPages,代码如下:
STDMETHODIMP CFilterTitleOverlay::GetPages(CAUUID *pPages)
{
pPages->cElems=1;
pPages->pElems=(GUID *)CoTaskMemAlloc(sizeof(GUID));
if(pPages->pElems==NULL)
{
return E_OUTOFMEMORY;
}
*(pPages->pElems)=CLSID_HQTitleOverlayProp;
return NOERROR;
}
并且在NonDelegatingQueryInterface函数中“暴露”ISpecifyPropertyPages接口,代码
如下:
STDMETHODIMP CFilterTitleOverlay::NonDelegatingQueryInterface(REFIID riid,
void **ppv)
{
CheckPointer(ppv,E_POINTER);
if(riid==IID_ISpecifyPropertyPages)//支持属性页的接口
{
return GetInterface((ITitleOverlay*)this,ppv);
}
else if(riid==IID_ITitleOverlay)
{
return GetInterface((ITitleOverlay *)this,ppv);
}
else
{
return CTransInPlaceFilter::NonDelegatingQueryInterface(riid,ppv);
}
}
逻辑控制类才是字符叠加应该真正实现的地方。下面首先介绍字符叠加原理。
一般输入的每个视频Sample(非压缩格式)都带有一帧图像数据,字符叠加实际上就
是将指定位置的图像像素值替换为字符图像的像素值。那么,怎么替换效率更高一点呢?
一种直观的想法是,将图像帧选入GDI的DC中,再使用GDI函数TextOut和DrawText等直接在
图像帧上输出字符,然后再取出图像帧并返回给Filter框架。实际上,这种做法的效率不是
理想,要想做到实时叠加难度很大。
我们采用的一种方法是,在内存中首先建立一个二色位图,然后在这个位图上画出字符
内容。于是,我们就得到了字符内容的一块点阵信息:比如0表示背景,1表示前景字符。在
实际叠加的时候,我们将图像帧指定位置的像素与字符点阵的像素位对应,如果字符点阵的
像素位值为0,则保持图像帧对应的像素值不变;如果为1,则将图像帧对应的像素替换为用
户设置的字符颜色值。
创建字符内容的二色位图的代码如下:
BOOL COverlayController::CreateTitleDIBBits(void)
{
//创建一个与桌面兼容的内存DC
HDC hdc=CreateCompatibleDC(NULL);
if(hdc)
{
//创建字符位图
HBITMAP hbm=ActualCreateTitleDIB(hdc);
DeleteDC(hdc);
if(hbm)
{
DeleteObject(hbm);
return TRUE;
}
}
return FALSE;
}
HBITMAP COverlayController::ActualCreateTitleDIB(HDC inDC)
{
//创建字符位图使用的DIB信息
//创建的位图初始为全黑背景,而在有输出的区域为白底黑字
struct{
BITMAPINFOHEADER bmiHeader;
DWORD rgbEntries[2];
}bmi=
{
{
sizeof(BITMAPINFOHEADER),
0,
0,
1,
1,
BI_RGB,
0,
0,
0
},
{
0x00000000,
0xffffffff
}
};
//设置字体
CAutoFont autoFont;
if(mIsFontChanged)
{
autoFont.CreateFont(mTitleFont);
autoFont.SelectToDC(inDC);
}
//得到整个字符内容的宽和高,以决定我们将要创建的位图的大小
GetTextExtentPoint32(inDC,mTitle,lstrlen(mTitle),&mTitleSize);
//给子类一个机会改变将要创建的位图的大小
if(!ValidateTitleDIBSize())
{
return NULL;
}
//设置将要创建的位图的大小
bmi.bmiHeader.biHeight=mTitleSize.cy;
bmi.bmiHeader.biWidth=mTitleSize.cx;
HBITMAP hbm=CreateDIBitmap(inDC,&bmi.bmiHeader,0,NULL,NULL,0);
BOOL pass=(hbm!=NULL);
if(pass)
{
//在位图上输出字符内容
HGDIOBJ hobj=SelectObject(inDC,hbm);
pass=ExtTextOut(inDC,0,0,ETO_OPAQUE|ETO_CLIPPED,NULL,
mTitle,lstrlen(mTitle),NULL);
SelectObject(inDC,hobj);
}
//获取位图的数据(相当于点阵信息)
if(pass)
{
ReleaseTitleDIB();
//Attention:To get bitmap data from the DIB object,
//the scan line must be a multiple of 4(DWORD)!
//If the actual bitmap data is not exactly fit for DWORD,
//The rest of DWORD bits will be filled automatically.
//So we should expand to bytes and round up to a multiple of 4.
mDIBWidthInBytes=((mTitleSize.cx+31)>>3)& ~3;
//上面4字节对齐公式的解释,>>3等于除于8就变成字节对齐,然后再
//和~3(即111111111111....11100)与,通过0x0100*n能得到最后两
//位为00的4的整数倍的数;最后两位为00的4的整数倍的数可以由0x0100*n
//计算得到,证明这个充分必要条件
mTitleDIBBits=new BYTE[mDIBWidthInBytes*mTitleSize.cy];
memset(mTitleDIBBits,0,mDIBWidthInBytes *mTitleSize.cy);
LONG lLines=GetDIBits(inDC,hbm,0,mTitleSize.cy,(PVOID)mTitleDIBBits,
(BITMAPINFO *)&bmi,DIB_RGB_COLORS);
pass=(lLines!=0);
}
if(!pass&&hbm)
{
DeleteObject(hbm);
hbm=NULL;
}
return hbm;
}
在实际的字符叠加过程中,还有一个问题,就是在设计逻辑控制类的时候,我们事先并不能
知道输入的图像到底是RGB的哪种格式。这需要在Filter的输入Pin连接完成后设置进来。然而,
不同的RGB格式每个像素使用的字节数不一样,像素值的替换方式也就不一样。我们希望用一种
统一的方式来控制。于是,我们对像素进行了抽象,设计了一个像素操作类结构。
class CBasePixel
{
protected:
unsigned char m_TargetR;//字符颜色的RGB值
unsigned char m_TargetG;
unsigned char m_TargetB;
unsigned char m_PixelSize;//每个像素使用的字节数
public:
CBasePixel();
~CBasePixel();
void SetTargetColor(unsigned char inR,unsigned char inG,unsigned char inB);
void SetPixelSize(int inSize);
int GetPixelSize(void){return m_PixelSize;}
//直接替换的叠加方式
virtual void ConvertByCover(unsigned char *inPixel);
//使用反色叠加,使前景的字符在背景视频图像上更突出
virtual void ConverByReverse(unsigned char *inPixel);
//指向下一个像素
unsigned char *NextPixel(unsigned char *inCurrent);
//指向下N个像素
unsigned char *NextNPixel(unsigned char *inCurrent,int inCount);
protected:
virtual void SideEffectColorChanged(void);
};
CBasePixel的子类分别实现各自的ConvertByCover函数,用于实际像素值的替换。
我们看到控制类COverlayController定义了一个CBasePixel类型的成员对象指针。根据输
入图像的具体格式,我们生成不同的CBasePixel子类对象实例。于是,我们可以统一调用
CBasePixel::ConvertByCover或CBasePixel::ConvertByReverse来完成像素值的替换。
我们再回到控制类的实现问题上。我们看到,控制类上定义了很多成员函数,用于参
数的设置。另外定义了3个重要的公共函数:
virtual BOOL StartTitleOverlay(void);//Filter进入运行状态时调用,以便初始化
virtual BOOL StopTitleOverlay(void);//Filter进入停止状态时调用,以便反初始化
BOOL DoTitleOverlay(PBYTE inImage);//真正的字符叠加函数
DoTitleOverlay的实现代码如下:
BOOL COverlayController::DoTitleOverlay(PBYTE inImage)
{
if(!mCanDoOverlay)
{
return FALSE;
}
BOOL pass=BeforeActualOverlay();//开始叠加前进必要的准备
if(pass)
{
pass=ActualOverlay(inImage);//真正的叠加
}
if(pass)
{
pass=AfterActualOverlay();//叠加后的“善后”工作
}
return pass;
}
其中,BeforeActualOverlay、ActualOverlay、AfterActualOverlay都是虚函数(子类都
可以重新实现)。之所以如此编写,是因为COverlayController是一个基类,更多的职责在于
设计应用框架,而留出虚函数让子类根据具体应用去定制。
ActualOverlay函数的实现如下:
BOOL COverlayController::ActualOverlay(PBYTE inImage)
{
//Now copy the data from the DIB section(which is usually bottom-up)
//but first check if it's too big
if(mImageWidth>mStartPos.x&&mImageHeight>mStartPos.y&&
mTitleSize.cx>0&&mTitleSize.cy>0)
{
long actualOverlayWidth=min((mImageWidth-mStartPos.x),mTitleSize.cx);
long actualOverlayHeight=min((mImageHeight-mStartPos.y),mTitleSize.cy);
//Image may be bottom-up, may be top-down
//Anyway retrieve the pointer which point to the top line
PBYTE pTopLine=NULL;
long strideInBytes=0;
if(mIsBottomUpImage)
{
strideInBytes=-mImageWidthInBytes;
pTopLine=inImage+mImageWidthInBytes*(mImage-Height-1);
}
else
{
strideInBytes=mImageWidthInBytes;
pTopLine=inImage;
}
PBYTE pStartPos=pTopLine+mStartPos.y*strideInBytes;
pStartPos+=(mStartPos.x *mImageBitCount/8);
for(DWORD dwY=0;dwY<(DWORD)actualOverlayHeight;dwY++)
{
PBYTE pbTitle=mTitleDIBBits;
pbTitle+=(mDIBWidthInBytes*((DWORD)mTitleSize.cy-dwY-1));
for(DWORD dwX=0;dwX<(DWORD)actualOverlayWidth;dwX++)
{
//dwX&7,value from 0-7
//0x80>>(dwX&7),value from 10000000 to 00000001
//dwX>>3,value add one every eight
//If the source bit is 0,the background.If 1,draw the text.
if(!((0x80>>(deX&7))&pbTitle[dwX>>3]))
{
PBYTE pbPixel=mPixelConverter->NextNPixel(pStartPos,dwX);
if(mIsOverlayByCover)
{
mPixelConverter->ConvertByCover(pbPixel);
}
else
{
mPixelConverter->ConvertByReverse(pbPixel);
}
}
}
pStartPos+=strideInBytes;
}
return TRUE;
}
return FALSE;
}
上述函数中,首先判断叠加位置的有效性;然后计算得到一个指向图像帧第一行数据
的指针。图像一般有两种扫描方式:从下往上(Bottom-Up)和从上往下(Top-Down)。前
者多用于GDI环境,图像数据存储时,最后一行的数据放在最前面,然后依次放倒数第2行的
数据。这时候计算指向图像第1行数据的指针,我们必须从图像数据开始地址计算一个偏移
量;后者多用于DirectDraw环境,是正常的图像数据存储格式。图像的扫描方式用
BITMAPINFOHEADER数据结构的biHeight成员值的正负来表示:正数表示从下往上,负数表示
从上往下。mIsBottomUpImage的取值如下:
mIsBottomUpImage=inInfoHeader->bmiHeader.biHeight>0?TRUE:FALSE;
再下来就是一个两层循环,完成了字符内容像素值在图像帧中对应位置的替换。
关于逻辑控制类的扩展问题,比如要叠加系统时间,我们就可以从COverlayController
中派生一个类CSysTimeOverlayController。因为系统时间是一直在变的,所以这个控制类的
特点是,每次叠加前都必须重新生成字符内容的点阵信息。我们主要通过重新实现
BeforeActualOverlay函数来完成,代码如下:
BOOL CSysTimeOverlayController::BeforeActualOverlay(void)
{
//更新当前系统时间
SYSTEMTIME systemTime,localTime;
GetSystemTime(&systemTime);//This is Coordinated Universal Time(UTC)
SystemTimeToTzSpecificLocalTime(NULL,&systemTime,&localTime);
sprintf(mTitle,"%4d-%02d-%02d(%02d:%02d:%02d)",localTime.wYear,localTime.wMonth,
localTime.wDay,localTime.wHour,localTime.wMinute,localTime.wSecond);
//重新创建字符点阵信息
BOOL pass=CreateTitleDIBBits();
return pass;
}
再比如我们要扩展滚动字幕叠加控制,同样也是从COverlayController中派生一个类
CScrollController。因为滚动字幕的叠加坐标以及字符内容的显示区域需要实时计算获得,这个
控制类的主要实现是,重写BeforeActualOverlay函数以便每次叠加前完成上述计算工作,并且重
写ActualOverlay函数,完成在动态指定的坐标上叠加有效区域的字符内容。代码如下:
BOOL CScrollController::BeforeActualOverlay(void)
{
BOOL pass=COverlayController::BeforeActualOverlay();
if(pass)
{
//Calculate for the next progress
long actualProgress=mOverlayCounter-mOverlayStartTime;
mStartPos.x=long(mImageWidth-1-mScrollStride*actualProgress);
if(mStartPos.x<0)
{
//Moving out of the left side
mValidTitleRect.left=-mStartPos.x;
if(mValidTitleRect.left>=mTitleSize.cx)
{
mValidTitleRect.left=mTitleSize.cx-1;
}
mStartPos.x=0;
if(mTitleSize.cx-mValidTitleRect.left<=mImageWidth)
{
mValidTitleRect.right=mTitleSize.cx-1;
}
else
{
mValidTitleRect.right=mValidTitleRect.left+mImageWidth;
}
}
else
{
//Moving in the image-width range
mValidTitleRect.left=0;
long currentLength=mImageWidth-mStartPos.x;
if(currentLength>=mTitleSize.cx)
{
mValidTitleRect.right=mTitleSize.cx-1;
}
else
{
mValidTitleRect.right=currentLength;
}
}
pass=(mValidTitleRect.right>mValidTitleRect.left);
}
return pass;
}
//mStartPos为每次叠加前计算所得的开始坐标
//mValidTitleRect为有效的字符区域
BOOL CScrollController::ActualOverlay(PBYTE inImage)
{
if(mImageHeight>mTitleSize.cy&&mTitleSize.cx>0&&
mTitleSize.cy>0)
{
//Image may be bottom-up, may be top-down
//Anyway retrieve the pointer which point to the top line
PBYTE pTopLine=NULL;
long strideInBytes=0;
if(mIsBottomUpImage)
{
strideInBytes=-mImageWidthInBytes;
pTopLine=inImage+mImageWidthInBytes*(mImage-Height-1);
}
else
{
strideInBytes=mImageWidthInBytes;
pTopLine=inImage;
}
PBYTE pStartPos=pTopLine+mStartPos.y*strideInBytes;
pStartPos+=(mStartPos.x*mImageBitCount/8);
for(DWORD dwY=0;dwY<(DWORD)mTitleSize.cy;dwY++)
{
PBYTE pbTitle=mTitleDIBBits;
pbTitle+=(mDIBWidthInBytes*((DWORD)mTitleSize.cy-dwY-1));
//Point to the valid start position of title DIB
pbTitle+=(mValidTitleRect.left>>3);
long startLeft=mValidTitleRect.left%8;
long endRight=startLeft+mValidTitleRect.right;
endRight-=mValidTitleRect.left;
for(long dwX=startLefr;dwX<endRight;dwX++)
{
if(!((0x80>>(dwX&7))&pbTitle[dwX>>3]))
{
PBYTE pbPixel=mPixelConverter->NextNPixel(pStartPos,dwX-startLeft);
if(mIsOverlayByCover)
{
mPixelConverter->ConvertByCover(pbPixel);
}
else
{
mPixelConverter->ConvertByReverse(pbPixel);
}
}
}
pStartPos+=strideInBytes;
}
}
return TRUE;
}
4.自定义接口的实现
自定义接口的实现其实很简单。首先定义接口方法,代码如下:
//ITitleOverlay.h
//Desc:DirectShow sample code - custom interface
#ifndef __H_ITitleOverlay__
#define __H_ITitleOverlay__
#ifdef __cplusplus
extern "C"{
#endif
//ITitleOverlay's GUID
//{5E5B3386-6F9F-4a47-AC8B-7A302138D7FF}
DEFINE_GUID(IID_ITitleOverlay,0x5e5b3386,0x6f9f,0x4a47,0xac,0x8b,
0x7a,0x30,0x21,0x38,0xd7,0xff);
//ITitleOverlay
DECLARE_INTERFACE_(ITitleOverlay,IUnknown)
{
//设置Filter进行叠加的类型,如果需要改变类型,必须第1个设置这个函数,
//调用这个函数成功后,才能调用其他的函数进行参数设置
//可以设置的叠加类型参见枚举类型OVERLAY_TYPE的定义
STDMETHOD(put_TitleOverlayType)(THIS_long inOverlayType)PURE;
STDMETHOD(get_TitleOverlayType)(THIS_long *outOverlayType)PURE;
//设置像素转换的方式(可选),默认使用直接替换方式
//当字符颜色与视频背景颜色接近时,使用翻转叠加方式比较合适
STDMETHOD(put_TitleOverlayStyle)(THIS_int inUsingCover)PURE;
STDMETHOD(get_TitleOverlayStyle)(THIS_int *outUsingCover)PURE;
//设置欲叠加字符的内容
STDMETHOD(put_Title)(THIS_const char* inTitle,int inLength)PURE;
STDMETHOD(get_Title)(THIS_char *outBuffer,int *outLength)PURE;
//设置欲叠加字符的颜色,分别为R,G,B分量值
STDMETHOD(put_TitleColor)(THIS_BYTE inR,BYTE inG,BYTE inB)PURE;
//设置欲叠加字符的开始坐标
STDMETHOD(put_TitleStartPosition)(THIS_POINT inStartPos)PURE;
STDMETHOD(get_TitleStartPosition)(THIS_POINT *outStartPos)PURE;
//设置欲叠加字符的字体,参数采用Windows GDI的LOGFONT数据结构
STDMETHOD(put_TitleFont)(THIS_LOGFONT inFont)PURE;
STDMETHOD(get_TitleFont)(THIS_LOGFONT *outFont)PURE;
//设置欲叠加字符的生存周期,一个参数是开始时间,一个参数是结束时间,以s为单位
//如果inEnd值为-1,表示一直迭加到视频结束
STDMETHOD(put_TitleDuration)(THIS_double inStart,double inEnd)PURE;
STDMETHOD(get_TitleDuration)(THIS_double *outStart,double *outEnd)PURE;
};
#ifdef __cplusplus
}
#endif
#endif//__H_ITitleOverlay__
然后将Filter类从ITitleOverlay派生,并声明ITitleOverlay的所有接口方法,代码如下:
//从ITitleOverlay中继承Filter类
class CFilterTitleOverlay:public CTransInPlaceFilter,public ITitleOverlay
{
//...
//---ITitleOverlay methods---
STDMETHODIMP put_TitleOverlayType(long inOverlayType);
STDMETHODIMP get_TitleOverlayType(long *outOverlayTyoe);
STDMETHODIMP put_TitleOverlayStyle(int inUsingCover);
STDMETHODIMP get_TitleOverlayStyle(int *outUsingCover);
STDMETHODIMP put_Title(const char*inTitle,int inLength);
STDMETHODIMP get_Title(char* outBuffer,int *outLength);
STDMETHODIMP put_TitleColor(BYTE inR,BYTE inG,BYTE inB);
STDMETHODIMP get_TitleColor(BYTE *outR,BYTE *outG,BYTE *outB);
STDMETHODIMP put_TitleStartPosition(POINT inStartPos);
STDMETHODIMP get_TitleStartPosition(POINT *outStartPos);
STDMETHODIMP put_TitleFont(LOGFONT inFont);
STDMETHODIMP get_TitleFont(LOGFONT *outFont);
STDMETHODIMP put_TitleDuration(double inStart,double inEnd);
STDMETHODIMP get_TitleDuration(double *outStart,double *outEnd);
}
在Filter类的实现文件中实现这些接口方法(其实大都是调用应用逻辑控制类的相应函数),
代码如下:
//---ITitleOverlay methods---
STDMETHODIMP CFilterTitleOverlay::put_TitleOverlayType(long inOverlayType)
{
CAutoLock lockit(&mITitleOverlaySync);
//When filter graph is active,change overlay type is not allowed!
if(m_State!=State Stopped)
{
return E_UNEXPECTED;
}
if(mOverlayType!=(OVERLAY_TYPE)inOverlayType)
{
mOverlayType=(OVERLAY_TYPE)inOverlayType;
SideEffectOverlayTypeChanged();
}
return NOERROR;
}
//当字符叠加的类型改变时,需要生成新的应用逻辑控制类对象实例,
//并且在新生成的控制对象上设置参数信息
void CFilterTitleOverlay::SideEffectOverlayTypeChanged(void)
{
ReleaseOverlayController();
switch(mOverlayType)
{
case OT_SYSTIME:
mOverlayController=new CSysTimeOverlayController();
break;
case OT_SCROLL_TOP:
mOverlayController=new CScrollController();
((CScrollController*)mOverlayController)->SetScrollBottomOrTop(FALSE);
break;
case OT_SCROLL_BOTTOM:
mOverlayController=new CScrollController();
((CScrollController*)mOverlayController)->SetScrollBottonOrTop(TRUE);
break;
case OT_NONE:
case OT_STATIC:
default:
mOverlayController=new COverlayController();
}
//Make sure to set input video info to the new controller
SetInputVideoInfoToController();
}
STDMETHODIMP CFilterTitleOverlay::get_TitleOverlayType(long *outOverlayType)
{
CAutoLock lockit(&mITitleOverlaySync);
*outOverlayType=mOverlayType;
return NOERROR;
}
STDMETHODIMP CFilterTitleOverlay::put_TitleOverlayStyle(int inUsingCover)
{
CAutoLock lockit(&mITitleOverlaySync);
mOverlayController->SetOverlayStyle(inUsingCover);
return NOERROR;
}
STDMETHODIMP CFilterTitleOverlay::get_TitleOverlayStyle(inr *outUsingCover)
{
CAutoLock lockit(&mITitleOverlaySync);
mOverlayController->GetOverlayStyle(outUsingCover);
return NOERROR;
}
STDMETHODIMP CFilterTitleOverlay::put_Title(const char*inTitle,int inLength)
{
CAutoLock lockit(&mITitleOverlaySync);
//When filter graph is active,change title text is not allowed!
if(m_State!=State_Stopped)
{
return E_UNEXPECTED;
}
mOverlayController->SetTitle(inTitle,inLength);
return NOERROR;
}
STDMETHODIMP CFilterTitleOverlay::get_Title(char *outBuffer,int *outLength)
{
CAutoLock lockit(&mITitleOverlaySync);
*outLength=mOverlayController->GetTitle(outBuffer);
return NOERROR;
}
STDMETHODIMP CFilterTitleOverlay::put_TitleColor(BYTE inR,BYTE inG,BYTE inB)
{
CAutoLock lockit(&mITitleOverlaySync);
mOverlayController->SetTitleColor(inR,inG,inB);
return NOERROR;
}
STDMETHODIMP CFilterTitleOverlay::get_TitleColor(BYTE *outR,BYTE *outG,BYTE *outB)
{
CAutoLock lockit(&mITitleOverlaySync);
mOverlayController->GetTitleColor(outR,outG,outB);
return NOERROR;
}
STDMETHODIMP CFilterTitleOverlay::put_TitleStartPosition(POINT inStartPos)
{
CAutoLock lockit(&mITitleOverlaySync);
mOverlayController->SetTitleStartPosition(inStartPos);
return NOERROR;
}
STDMETHODIMP CFilterTitleOverlay::get_TitleStartPosition(POINT *outStartPos)
{
CAutoLock lockit(&mITitleOverlaySync);
mOverlayController->GetTitleStartPosition(outStartPos);
return NOERROR;
}
STDMETHODIMP CFilterTitleOverlay::put_TitleFont(LOGFONT inFont)
{
CAutoLock lockit(&mITitleOverlaySync);
mOverlayController->SetTitleFont(outFont);
return NOERROR;
}
STDMETHODIMP CFilterTitleOverlay::put_TitleDuration(double inStart,double inEnd)
{
CAutoLock lockit(&mITitleOverlaySync);
mOverlayController->SetTitleDuration(inStart,inEnd);
return NOERROR;
}
STDMETHODIMP CFilterTitleOverlay::get_TitleDuration(double *outStart,double *outEnd)
{
CAutoLock lockit(&mITitleOverlaySync);
mOverlayController->GetTitleDuration(outStart,outEnd);
return NOERROR;
}
最后,在NonDelegatingQueryInterface函数中“暴露”我们的ITitleOverlay接口,代码如下:
STDMETHODIMP CFilterTitleOverlay::NonDelegatingQueryInterface(REFIID riid,void **ppv)
{
checkPointer(ppv,E_POINTER);
if(riid==IID_ITitleOverlay)
{
return GetInterface((ITitleOverlay*)this,ppv);
}
else
{
return CTransInPlaceFilter::NonDelegatingQueryInterface(riid,ppv);
}
}
属性页的实现
属性页是用户与Filter交互的简单界面(一般在商业软件中不推荐将Filter的属性页直接显示给用户)。
Filter属性页更多地用于开发时候的接口调试。当然,Filter也可以不实现属性页。
在“字符叠加Filter”中,我们首先用VC的资源编辑器生成一个对话框,并编辑好对话框的内容。
然后从CBasePropertyPage类中派生一个子类CTitleOverlayProp,用于实现我们的属性页。在CTitleOverlayProp
类实现中,我们主要重写了如下函数:
//创建属性页COM对象实例
CUnknown *WINAPI CTitleOverlayProp::CreateInstance(LPUNKNOWN lpunk,HRESULT *phr)
{
CUnknown *punk=new CTitleOverlayProp(lpunk,phr);
if(punk==NULL)
{
*phr=E_OUTOFMEMORY;
}
return punk;
}
//获取ITitleOverlay接口
HRESULT CTitleOverlayProp::OnConnect(IUnknown *pUnknown)
{
HRESULT hr=pUnknown->QueryInterface(IID_ITitleOverlay,(void **)&mIOverlay);
if(FAILED(hr))
{
return E_NOINTERFACE;
}
ASSERT(mIOverlay);
return NOERROR;
}
//释放ITitleOverlay接口
HRESULT CTitleOverlayProp::OnDisconnect()
{
//Release of Interface
if(mIOverlay==NULL)
{
return E_UNEXPECTED;
}
mIOverlay->Release();
mIOverlay=NULL;
return NOERROR;
}
//通过ITitleOverlay接口取得Filter上的各参数,并在属性页界面上显示
HRESULT CTitleOverlayProp::OnActivate()
{
FillOverlayTypeComboBox();
ReflectOverlayType();
ReflectOverlayStyle();
ReflectTitle();
ReflectTitleStartPosition();
ReflectTitleDuration();
ReflectTitleColor();
ReflectTitleFont();
return NOERROR;
}
//将属性页界面上用户设置的各参数,通过ITitleOverlay接口设置给Filter
HRESULT CTitleOverlayProp::OnApplyChanges()
{
EnterOverlayType();//This must be first invoked!!
EnterOverlayStyle();
EnterTitle();
EnterTitleStartPosition();
EnterTitleDuration();
EnterTitleColor();
EnterTitleFont();
return NOERROR;
}
注意,上述OnActivate和OnApplyChanges函数实现中调用的各函数都是CtitleOverlay Prop
实现的私有成员函数,用于操作各个参数的读取和设置。代码如下:
void CTitleOverlayProp::ReflectTitle(void)
{
int titleLength=0;
mIOverlay->get_Title(NULL,&titleLength);//获取字符长度
if(titleLength>0)
{
char *szTitle=new char[titleLength];
mIOverlay->get_Title(szTitle,&titleLength);//获得字符内容
SetWindowText(m_hEditTitle,szTitle);
delete[] szTitle;
}
}
void CTitleOverlayProp::EnterTitle(void)
{
char szTitle[2000];
//设置字符内容
if(GetWindowText(m_hEditTitle,szTitle,2000))
{
mIOverlay->put_Title(szTitle,strlen(szTitle));
}
else
{
mIOverlay->put_Title("",0);
}
}
另外,我们还实现了OnReceiveMessage函数,用于处理属性页窗口的消息,代码如下:
BOOL CTitleOverlayProp::OnReceiveMessage(HWND hwnd,
UINT uMsg,WPARAM wParam,LPARAM lParam)
{
switch(uMsg)
{
case WM_INITDIALOG:
{
//得到各界面元素的窗口句柄
m_hOverlayType=GetDlgItem(hwnd,IDC_COMBO_OVERLAY_TYPE);
m_hEditTitle=GetDlgItem(hwnd,IDC_EDIT_TITLE);
m_hEditStartX=GetDlgItem(hwnd,IDC_EDIT_STARIX);
m_hEditStartY=GetDlgItem(hwnd,IDC_EDIT_STARTY);
m_hEditStartTime=GetDlgItem(hwnd,IDC_EDIT_STARTTIME);
m_hEditEndTime=GetDlgItem(hwnd,IDC_EDIT_ENDTIME);
m_hEditColorR=GetDlgItem(hwnd,IDC_EDIT_COLORR);
m_hEditColorG=GetDlgItem(hwnd,IDC_EDIT_COLORG);
m_hEditColorB=GetDlgItem(hwnd,IDC_EDIT_COLORB);
break;
}
case WM_COMMAND:
{
if(HIWORD(wParam)==BN_CLICKED)
{
switch(LOWORD(wParam))
{
//改变字体
case IDC_BUTTON_CHANGE_FONT:
OnButtonChangeFont();
break;
}
}
SetDirty();
break;
}
}
return CBasePropertyPage::OnReceiveMessage(hwnd,uMsg,wParam,lParam);
}
其中,OnButtonChangeFont函数实现为弹出Windows标准的字体选择框,代码如下:
void CTitleOverlayProp::OnButtonChangeFont(void)
{
//Update the RGB values
BYTE colorR=0,colorG=0,colorB=0;
char szColor[100];
GetWindowText(m_hEditColorR,szColor,100);
colorR=atoi(szColor);
GetWindowText(m_hEditColorG,szColor,100);
colorG=atoi(szColor);
GetWindowText(m_hEditColorB,szColor,100);
colorB=atoi(szColor);
mTitleColor=RGB(colorR,colorG,colorB);
CHOOSEFONT cf;
//Initialize CHOOSEFONT
ZeroMemory(&cf,sizeof(CHOOSEFONT));
cf.lStructSize=sizeof(CHOOSEFONT);
cf.hInstance=g_hInst;
cf.hwndOwner=m_hwnd;
cf.lpLogFont=&mTitleFont;
cf.rgbColors=mTitleColor;
cf.Flags=CF_SCREENFONTS|CF_EFFECTS|CF_INITTOLOGFONTSTRUCT;
//调用标准的选择字体的API函数
if(ChooseFont(&cf))
{
mIsFontChanged=TRUE;
//Update the text color
mTitleColor=cf.rgbColors;
ReflectTitleColor(GetRValue(mTitleColor),GetGValue(mTitleColor),
GetBValude(mTitleColor));
}
}
最后在Filter上实现ISpecifyPropertyPages的接口方法GetPages,代码如下:
STDMETHODIMP CFilterTitleOverlay::GetPages(CAUUID *pPages)
{
pPages->cElems=1;
pPages->pElems=(GUID *)CoTaskMemAlloc(sizeof(GUID));
if(pPages->pElems==NULL)
{
return E_OUTOFMEMORY;
}
*(pPages->pElems)=CLSID_HQTitleOverlayProp;
return NOERROR;
}
并且在NonDelegatingQueryInterface函数中“暴露”ISpecifyPropertyPages接口,代码
如下:
STDMETHODIMP CFilterTitleOverlay::NonDelegatingQueryInterface(REFIID riid,
void **ppv)
{
CheckPointer(ppv,E_POINTER);
if(riid==IID_ISpecifyPropertyPages)//支持属性页的接口
{
return GetInterface((ITitleOverlay*)this,ppv);
}
else if(riid==IID_ITitleOverlay)
{
return GetInterface((ITitleOverlay *)this,ppv);
}
else
{
return CTransInPlaceFilter::NonDelegatingQueryInterface(riid,ppv);
}
}
5.产权保护
一种对Filter产权保护的简单实现方法,即在Filter创建的时候判断一下创建者是否“合法”————我
们的Filter只允许被GraphEdit和自己的应用程序创建。
CUnknown *WINAPI CFilterTitleOverlay::CreateInstance(LPUNKNOWN punk,HRESULT *phr)
{
#if 1
char szCreatorPath[256],szCreatorName[256];
::strcpy(szCreatotPath,"");
::strcpy(szCreatorName,"");
HMODULE hModule=::GetModuleHandle(NULL);
::GetModuleFileName(hModule,szCreatorPath,256);
char *backSlash=::strrchr(szCreatorPath,'\\');
if(backSlash)
{
strcpy(szCreatorName,backSlash);
}
::_strlwr(szCreatorName);
//Please specify your app name with lowercase
if(::strstr(szCreatorName,"graphedt")==NULL&&
::strstr(szCreatorName,"Ourapp")==NULL)
{
*phr=E_FAIL;
return NULL;
}
#endif
CFilterTitleOverlay *pNewObject=new
CFilterTitleOverlay(NAME("TitleOverlay"),punk,phr);
if(pNewObject==NULL)
{
*phr=E_OUTOFMEMORY;
}
return pNewObject;
}
其中,ourapp应该指定为我们的应用程序名字。
实际上,上述方法并不能很好地保护Filter。如果以二进制方式打开Filter的AX文件,可以查到
graphedt和outapp字符串。只需将其中一个字符串改成其他应用程序的名字,我们Filter的合法创建
者就被篡改了。
一种对Filter产权保护的简单实现方法,即在Filter创建的时候判断一下创建者是否“合法”————我
们的Filter只允许被GraphEdit和自己的应用程序创建。
CUnknown *WINAPI CFilterTitleOverlay::CreateInstance(LPUNKNOWN punk,HRESULT *phr)
{
#if 1
char szCreatorPath[256],szCreatorName[256];
::strcpy(szCreatotPath,"");
::strcpy(szCreatorName,"");
HMODULE hModule=::GetModuleHandle(NULL);
::GetModuleFileName(hModule,szCreatorPath,256);
char *backSlash=::strrchr(szCreatorPath,'\\');
if(backSlash)
{
strcpy(szCreatorName,backSlash);
}
::_strlwr(szCreatorName);
//Please specify your app name with lowercase
if(::strstr(szCreatorName,"graphedt")==NULL&&
::strstr(szCreatorName,"Ourapp")==NULL)
{
*phr=E_FAIL;
return NULL;
}
#endif
CFilterTitleOverlay *pNewObject=new
CFilterTitleOverlay(NAME("TitleOverlay"),punk,phr);
if(pNewObject==NULL)
{
*phr=E_OUTOFMEMORY;
}
return pNewObject;
}
其中,ourapp应该指定为我们的应用程序名字。
实际上,上述方法并不能很好地保护Filter。如果以二进制方式打开Filter的AX文件,可以查到
graphedt和outapp字符串。只需将其中一个字符串改成其他应用程序的名字,我们Filter的合法创建
者就被篡改了。