如何编写Photoshop滤镜(2)
设计我们的滤波器参数。
我们的滤镜完成了一个最基本的任务,就是“填充”,所以我们可以配置填充的颜色,此外,我们还可以设置填充颜色的不透明度。因此,我们引入以下参数并将其定义为一个struct,包括一个RGB填充颜色和一个不透明度(0~100):
//======================================
//定义我们的参数
//======================================
typedef struct _MYPARAMS
{
COLORREF fillColor//填充颜色
int不透明度;//百分比(0~100)
} MYPARAMS
(2)现在我们添加一个对话框资源。编辑对话框模块如下所示。然后我们为主控件设置控件ID。
注意,编辑完资源文件后,VC会重写rc文件,所以在编译项目前,我们需要手动打开rc文件,再次添加# include“fillred . pipl”。
否则,PS将无法正确识别编译后的滤镜并将其加载到滤镜菜单中。
(3)让我们为这个对话框添加一个窗口程序。为此,我们将ParamDlg.h和ParamDlg.cpp文件添加到项目中。
请注意,因为窗口过程位于我们的DLL中,所以我们必须将窗口过程声明为DLL导出函数,以便让系统知道函数的地址。
窗口程序的编写完全属于windows编程领域(这方面的知识请参考相关书籍),这里就不详细介绍如何编写窗口程序了。但值得一提的是,我在PS中引入了一个UI特性,就是它在PS中的字体设置对话框。当鼠标悬停在控件前面的标签(静态标签)上时,光标形状可以更改为特殊光标。左右按住拖动鼠标,相关控件的值会根据鼠标移动的方向自动增减,类似于滑块控件的效果。所以我在窗口进程中加入了这个功能,这样会让窗口进程的代码看起来复杂一点,但是这个功能(大概是PS发明的?)很有意思,很新颖。出于这个原因,我还引入了一个定制的光标文件。具体代码不贴了,请参考项目源代码中ParamDlg.cpp文件中的代码。
(4)基于第一篇文章,我们需要重写FillRed.cpp中的一些代码。
因为现在我们引入了不透明度参数,不透明度算法为:(不透明度= 0~ 100)。
结果值=输入值* (1-不透明度* 0.01)+填充颜色*不透明度* 0.01;
(a)对于DoStart和DoContinue:我们需要知道原始图片中的原始颜色,这样我们的inRect和inHiPlane将不再是空的矩形。这反映在DoStart和DoContinue函数中。我们修改inRect和inHiPlane使之与outRect和outHiPlane一致,这样PS就会通过inData把原始的图像数据发送给我们。
(b)当用户点击过滤菜单时,会以参数调用开始,所以我们会在这里设置一个标记,表示需要显示对话框。
(c)当用户点击“最近过滤”菜单时,会从准备调用开始,也就是说我们不需要显示对话框,而是直接取之前的缓存参数。为此,我们引入ReadParams和WriteParams函数。也就是我们使用PS提供的回调函数集,在我们的参数和PS脚本系统之间交换数据。
让我们来看看DoContinue函数中发生的变化。主要改变算法,inRect和inHiPlane的数据改为请求PS发送数据。第一个补丁是在DoStart()函数中设置的,对inRect和inHiPlane的修改是一样的。同时,在DoStart函数中,根据预先设置的标志决定是否显示对话框。
//DLLMain
BOOL API entry DllMain(HMODULE HMODULE,
呼叫的原因,
LPVOID lpReserved
)
{
dllInstance = static _ cast & ltHINSTANCE & gt(hModule);
if(ul _ reason _ for _ call = = DLL _ PROCESS _ ATTACH | | ul _ reason _ for _ call = = DLL _ THREAD _ ATTACH)
{
//加载DLL时初始化我们的参数!
gParams.fillColor = RGB(0,0,255);
gparams . opacity = 100;
}
返回TRUE
}
#ifdef _MANAGED
#pragma托管(pop)
#endif
//===================================================================================================
由ps调用的// -函数。
//===================================================================================================
DLLExport void plugin main(const int 16选择器,void * filterRecord,int32 *data,int16 *result)
{
gData =数据;
gResult =结果;
gFilterRecord =(FilterRecordPtr)filter record;
if(selector = = filterSelectorAbout)
ssp basic =((about record *)gFilterRecord)-& gt;sSPBasic
其他
ssp basic = gFilterRecord-& gt;sSPBasic
开关(选择器)
{
案例筛选器选择器关于:
do about();
打破;
案例筛选器选择器参数:
DoParameters();
打破;
案例筛选器选择器准备:
DoPrepare();
打破;
案例筛选器选择或开始:
do start();
打破;
案例筛选器选择或继续:
DoContinue();
打破;
案例筛选器选择或完成:
do finish();
打破;
默认值:
* gResult = filterBadParameters
打破;
}
}
//显示“关于”对话框
void DoAbout()
{
about record * about ptr =(about record *)gFilterRecord;
PlatformData * platform =(platform data *)(about ptr-& gt;platform data);
HWND hwnd = (HWND)平台-& gt;hwnd
MessageBox(hwnd,“FillRed滤镜:fill color - by hoodlum1980”,“关于FillRed”,MB _ OK);
}
//在此准备参数。就这个滤镜而言,我们暂时不需要做什么。
空DoParameters()
{
//参数调用描述,用户点击原菜单,要求显示对话框。
m _ ShowUI = TRUE
//设置参数地址
if(gFilterRecord-& gt;参数== NULL)
gFilterRecord-& gt;参数=(Handle)(& amp;gParams);
}
//告诉PS(主机)过滤器此时需要的内存大小。
void DoPrepare()
{
if(gFilterRecord!=空)
{
gFilterRecord-& gt;buffer space = 0;
gFilterRecord-& gt;maxSpace = 0;
//设置参数地址
if(gFilterRecord-& gt;参数== NULL)
gFilterRecord-& gt;参数=(Handle)(& amp;gParams);
}
}
//inRect:过滤器请求PS发送的矩形区域。
//outRect:滤镜通知PS接收的矩形区域。
//filterRect: PS通知滤镜要处理的矩形区域。
//因为我们用固定的红色来填充,所以实际上并不需要要求PS发送数据。
//所以这里可以将inRect设置为NULL,PS不会将数据传递给过滤器。
void DoStart()
{
BOOL showDialog
if(gFilterRecord == NULL)
返回;
//将参数值从脚本系统读入gParams。
OSErr err = read params(& amp;showDialog);
//需要显示对话框吗?
如果(!错误& amp& ampshowDialog)
{
platform data * platform =(platform data *)(gFilterRecord-& gt;platform data);
HWND HWND parent =(HWND)platform-& gt;hwnd
//显示对话框
int n result = DialogBoxParam(dllininstance,MAKEINTRESOURCE(IDD_PARAMDLG),hWndParent,(DLGPROC)ParamDlgProc,0);
if(nResult == IDCANCEL)
{
//取消被选中。
ZeroPsRect(& amp;gFilterRecord-& gt;in rect);
ZeroPsRect(& amp;gFilterRecord-& gt;outRect);
ZeroPsRect(& amp;gFilterRecord-& gt;mask rect);
write params();
//注意:(1)如果通知PS用户选择取消,PS不会发起呼叫完成!
// (2)只要start调用成功,PS保证会发起Finish调用。
* gResult = userCanceledErr
返回;
}
}
//我们初始化第一个图块,然后开始调用。
m _ tile . left = gFilterRecord-& gt;filterRect.left
m _ tile . top = gFilterRecord-& gt;filterRect.top
m _ tile . right = min(m _ tile . left+tile size,gFilterRecord-& gt;filter rect . right);
m _ tile . bottom = min(m _ tile . top+tile size,gFilterRecord-& gt;filter rect . bottom);
//Set inRect,outRect
//ZeroPsRect(& amp;gFilterRecord-& gt;in rect);//我们不需要PS告诉我们原图上是什么颜色,因为我们只要填进去就行了。
copy psrect(& amp;m _ Tile & amp;gFilterRecord-& gt;in rect);//现在我们需要请求与outRect相同的区域。
copy psrect(& amp;m _ Tile & amp;gFilterRecord-& gt;outRect);
//请求所有通道(然后数据被交织)
gFilterRecord-& gt;inlo plane = 0;
gFilterRecord-& gt;inhi plane =(gFilterRecord-& gt;飞机-1);;
gFilterRecord-& gt;out loplane = 0;
gFilterRecord-& gt;outh iplane =(gFilterRecord-& gt;飞机-1);
}
//当前补丁在这里处理。请注意,如果用户按Esc,下一个呼叫将结束。
void DoContinue()
{
int索引;//像素索引
if(gFilterRecord == NULL)
返回;
//定位像素
int planes = gFilterRecord-& gt;outh iplane-gFilterRecord-& gt;out loplane+1;//通道数量
//填充颜色
uint 8 r = getr value(gparams . fill color);
uint 8g = GetGValue(gparams . fill color);
uint 8 b = getb value(gparams . fill color);
int opacity = gParams.opacity
uint 8 * pDataIn =(uint 8 *)gFilterRecord-& gt;inData
uint 8 * pdata out =(uint 8 *)gFilterRecord-& gt;outData
//扫描线宽度(字节)
int stride = gFilterRecord-& gt;outRowBytes
//我们将输出矩形复制到m_Tile。
copy psrect(& amp;gFilterRecord-& gt;outRect & amp;m _ Tile);
for(int j = 0;j & lt(m _ tile . bottom-m _ tile . top);j++)
{
for(int I = 0;我& lt(m _ tile . right-m _ tile . left);i++)
{
指数= i *平面+j *步幅;
//为了简单起见,我们默认将图像视为RGB格式(其实不应该这么做)。
pDataOut[ index ] =
(uint 8)((pDataIn[index]*(100-不透明度)+r *不透明度)/100);//红色
pDataOut[ index+1 ] =
(uint 8)((pDataIn[index+1]*(100-不透明度)+g *不透明度)/100);//绿色
pDataOut[ index+2 ] =
(uint 8)((pDataIn[index+2]*(100-不透明度)+b *不透明度)/100);//蓝色
}
}
//判断是否已经处理。
if(m _ tile . right & gt;= gFilterRecord-& gt;filterRect.right & amp& ampm _ Tile.bottom & gt= gFilterRecord-& gt;filterRect.bottom)
{
//处理结束
ZeroPsRect(& amp;gFilterRecord-& gt;in rect);
ZeroPsRect(& amp;gFilterRecord-& gt;outRect);
ZeroPsRect(& amp;gFilterRecord-& gt;mask rect);
返回;
}
//设置下一个图块
if(m _ tile . right & lt;gFilterRecord-& gt;filterRect.right)
{
//向右移动一格
m _ tile . left = m _ tile . right;
m _ tile . right = min(m _ tile . right+tile size,gFilterRecord-& gt;filter rect . right);
}
其他
{
//向下换行并返回到行首
m _ tile . left = gFilterRecord-& gt;filterRect.left
m _ tile . right = min(m _ tile . left+tile size,gFilterRecord-& gt;filter rect . right);
m _ tile . top = m _ tile . bottom;
m _ tile . bottom = min(m _ tile . bottom+tile size,gFilterRecord-& gt;filter rect . bottom);
}
//ZeroPsRect(& amp;gFilterRecord-& gt;in rect);
copy psrect(& amp;m _ Tile & amp;gFilterRecord-& gt;in rect);//现在我们需要请求与outRect相同的区域。
copy psrect(& amp;m _ Tile & amp;gFilterRecord-& gt;outRect);
//请求所有通道(然后数据被交织)
gFilterRecord-& gt;inlo plane = 0;
gFilterRecord-& gt;inhi plane =(gFilterRecord-& gt;飞机-1);;
gFilterRecord-& gt;out loplane = 0;
gFilterRecord-& gt;outh iplane =(gFilterRecord-& gt;飞机-1);
}
//处理结束。我们暂时不需要在这里做任何事情。
void DoFinish()
{
//清除UI需要显示的标志。
m _ ShowUI = FALSE
//记录参数
write params();
} (5)从PS脚本系统中读写我们的参数。我们将ParamsScripting.h和ParamsScripting.cpp添加到项目中。代码如下。介绍了ReadParams和WriteParams方法。这一节主要涉及PS的描述符回调函数集,比较复杂,由于精力在这里就不多解释了。具体可以参考我之前的随笔中一篇关于PS回调函数集和代码注释的文章。相关代码如下:
#包含" stdafx.h "
#包含" ParamsScripting.h "
# include & ltstdio.h & gt
OSErr read params(BOOL * showDialog)
{
OSErr err = noErr
PIReadDescriptor token = NULL//读取运算符
DescriptorKeyID key = NULL//uint32,即char*,键名。
DescriptorTypeID type = NULL
int32标志= 0;
int32 intValue//接收返回值
char text[128];
//要读取的密钥
DescriptorKeyIDArray keys = { KEY _ fill color,KEY_OPACITY,NULL };
如果(showDialog!=空)
* showDialog = m _ ShowUI
//用于录制和播放,用于录制和播放动作。
PIDescriptorParameters * desc params = gFilterRecord-& gt;描述符参数;
if (descParams == NULL)
返回err
read descriptor procs * read procs = gFilterRecord-& gt;描述符参数-& gt;readDescriptorProcs
if (readProcs == NULL)
返回err
if(desc params-& gt;描述符!=空)
{