如何编写Photoshop滤镜(2)

在上一篇文章中,我们解释了如何创建Photoshop滤镜项目,以及如何为滤镜嵌入PIPL资源,以便PS可以识别和加载滤镜。并且我们建立了最简单最基本的过滤器框架。在本文中,我们将细化过滤器和PS之间的调用流,我们将为过滤器引入一个对话框资源,以便用户可以配置过滤器的自定义参数。并且我们会看到用户从不同的菜单位置调用滤镜时的进程差异,然后我们会介绍PS脚本描述系统对我们滤镜参数的读写支持,将我们的参数存储在PS脚本系统中,在以后的调用中读取这些参数。

设计我们的滤波器参数。

我们的滤镜完成了一个最基本的任务,就是“填充”,所以我们可以配置填充的颜色,此外,我们还可以设置填充颜色的不透明度。因此,我们引入以下参数并将其定义为一个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;描述符!=空)

{