在上一篇文章中,我们讲解了怎样创建一个photoshop滤镜的项目,以及如何为滤镜嵌入pipl资源使滤镜可以被ps识别和加载。并且我们已经建立了一个最简单最基本的滤镜框架。在这篇文章中,我们将细化滤镜和ps之间的调用流程,我们将为滤镜引入一个对话框资源,使用户可以对滤镜进行自定义参数的配置。并且我们将看到当用户从不同菜单位置发起滤镜调用时的流程区别,然后我们还将为我们的滤镜参数引入ps脚本描述系统的读写支持,将我们的参数存入ps的脚本系统中,并在以后的调用中读取出这些参数。
(1)设计我们的滤镜参数。
我们的滤镜完成的是一个最基本的任务,仅仅是“填充”,因此我们可以对填充的颜色进行配置,此外,我们还可以设置填充颜色的不透明度。因此我们引入下面的参数,把它定义为一个struct,包括一个rgb填充色,和一个不透明度(0~100):
//======================================
// 定义我们的参数
//======================================
typedef struct _myparams
{
colorref fillcolor; //填充颜色
int opacity; //百分比(0~100)
} myparams;
(2) 现在我们添加一个对话框资源。编辑对话框模块如下所示。然后我们对主要控件设置控件id。
【注意】编辑资源文件后,由于vc将会重写rc文件,因此在编译项目前,我们还需要手工打开rc文件,自己重新添加#include "fillred.pipl"。
否则编译好的滤镜将无法被ps正确识别和加载到滤镜菜单。
(3)下面我们为该对话框添加窗口过程。为此我们为项目添加 paramdlg.h 和 paramdlg.cpp文件。
【注意】由于窗口过程位于我们的dll中,因此我们必须把窗口过程声明为dll导出函数,以便让系统知道该函数的地址。
关于窗口过程的编写则完全属于 windows 编程领域的内容(这方面的知识可以参考相关书籍),这里我们不详细介绍怎样写窗口过程。但值得一提的是,我在这里引入了一个ps中的ui特性,即ps中例如它的字体设置对话框,当鼠标悬停在控件前面的lable(static标签)上方时,光标形状可以改变为特殊光标,按下并左右拖动鼠标,则相关控件的值就会根据鼠标移动方向自动增加或减小,类似slider控件的效果。因此我在窗口过程中为它加入了这个特性,这会使得窗口过程的代码看起来稍显复杂一些,不过这个功能(可能是ps发明的?)很有趣也很新颖。为此我还引入了一个自定义的光标文件。具体代码不贴出了,请参考项目源代码中的 paramdlg.cpp文件中的代码。
(4)在第一篇文章的基础上,我们需要改写fillred.cpp中的一些代码。
因为现在我们引入了不透明度参数,不透明度的算法是:(opacity = 0~ 100)
结果值 = 输入值 * (1- opacity*0.01) + 填充颜色 * opacity*0.01;
(a)对dostart 和 docontinue:我们需要知道原图中原来的颜色,因此我们的 inrect 和 inhiplane 将不在为空矩形。这体现在 dostart 和 docontinue 函数中,我们对inrect 和 inhiplane 修改为和 outrect , outhiplane 一致,这样ps就会把原图数据通过 indata 发送给我们。
(b)当用户点击滤镜菜单时,将从 parameter 调用开始,这样我们就在这里设置一个标记,表示需要显示对话框。
(c)当用户点击“最近滤镜”菜单时,将从 prepare 调用开始,这样表示我们不需要显示对话框,而是直接取此前的缓存参数。为此我们引入 readparams 和 writeparams 函数。即使用ps提供的回调函数集使我们的参数和ps scripting system进行交换数据。
下面我们主要看一下docontinue函数发生的变化。主要是对算法进行了改动,对 inrect , inhiplane 这两个数据进行了变动,以请求ps发送数据。在dostart()函数中设置了第一个贴片,对inrect 和 inhiplane 的改动是同样的。同时,在dostart函数中, 根据事先设置过的标志,来决定是否显示对话框。
//dllmain
bool apientry dllmain( hmodule hmodule,
dword ul_reason_for_call,
lpvoid lpreserved
)
{
dllinstance = static_cast<hinstance>(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;
}
return true;
}
#ifdef _managed
#pragma managed(pop)
#endif
//===================================================================================================
//------------------------------------ 滤镜被ps调用的函数 -------------------------------------------
//===================================================================================================
dllexport void pluginmain(const int16 selector, void * filterrecord, int32 *data, int16 *result)
{
gdata = data;
gresult = result;
gfilterrecord = (filterrecordptr)filterrecord;
if (selector == filterselectorabout)
sspbasic = ((aboutrecord*)gfilterrecord)->sspbasic;
else
sspbasic = gfilterrecord->sspbasic;
switch (selector)
{
case filterselectorabout:
doabout();
break;
case filterselectorparameters:
doparameters();
break;
case filterselectorprepare:
doprepare();
break;
case filterselectorstart:
dostart();
break;
case filterselectorcontinue:
docontinue();
break;
case filterselectorfinish:
dofinish();
break;
default:
*gresult = filterbadparameters;
break;
}
}
//显示关于对话框
void doabout()
{
aboutrecord *aboutptr = (aboutrecord*)gfilterrecord;
platformdata *platform = (platformdata*)(aboutptr->platformdata);
hwnd hwnd = (hwnd)platform->hwnd;
messagebox(hwnd, "fillred filter: 填充颜色 -- by hoodlum1980", "关于 fillred", mb_ok);
}
//这里准备参数,就这个滤镜例子来说,我们暂时不需要做任何事
void doparameters()
{
//parameter调用说明,用户点击的是原始菜单,要求显示对话框
m_showui = true;
//设置参数地址
if(gfilterrecord->parameters == null)
gfilterrecord->parameters = (handle)(&gparams);
}
//在此时告诉ps(宿主)滤镜需要的内存大小
void doprepare()
{
if(gfilterrecord != null)
{
gfilterrecord->bufferspace = 0;
gfilterrecord->maxspace = 0;
//设置参数地址
if(gfilterrecord->parameters == null)
gfilterrecord->parameters = (handle)(&gparams);
}
}
//inrect : 滤镜请求ps发送的矩形区域。
//outrect : 滤镜通知ps接收的矩形区域。
//filterrect : ps通知滤镜需要处理的矩形区域。
//由于我们是使用固定的红色进行填充,实际上我们不需要请求ps发送数据
//所以这里可以把inrect设置为null,则ps不向滤镜传递数据。
void dostart()
{
bool showdialog;
if(gfilterrecord == null)
return;
//从scripting system 中读取参数值到gparams中。
oserr err = readparams(&showdialog);
//是否需要显示对话框
if(!err && showdialog)
{
platformdata* platform = (platformdata*)(gfilterrecord->platformdata);
hwnd hwndparent = (hwnd)platform->hwnd;
//显示对话框
int nresult = dialogboxparam(dllinstance, makeintresource(idd_paramdlg),hwndparent,(dlgproc)paramdlgproc, 0);
if(nresult == idcancel)
{
//选择了取消
zeropsrect(&gfilterrecord->inrect);
zeropsrect(&gfilterrecord->outrect);
zeropsrect(&gfilterrecord->maskrect);
writeparams();
//注意: (1)如果通知 ps 用户选择了取消,将使ps不会发起 finish调用!
// (2)只要 start 调用成功,则ps保证一定发起 finish 调用。
*gresult = usercancelederr;
return;
}
}
//我们初始化第一个tile,然后开始进行调用
m_tile.left = gfilterrecord->filterrect.left;
m_tile.top = gfilterrecord->filterrect.top;
m_tile.right = min(m_tile.left + tilesize, gfilterrecord->filterrect.right);
m_tile.bottom = min(m_tile.top + tilesize, gfilterrecord->filterrect.bottom);
//设置inrect, outrect
//zeropsrect(&gfilterrecord->inrect); //我们不需要ps告诉我们原图上是什么颜色,因为我们只是填充
copypsrect(&m_tile, &gfilterrecord->inrect);//现在我们需要请求和outrect一样的区域
copypsrect(&m_tile, &gfilterrecord->outrect);
//请求全部通道(则数据为interleave分布)
gfilterrecord->inloplane = 0;
gfilterrecord->inhiplane = (gfilterrecord->planes -1);;
gfilterrecord->outloplane = 0;
gfilterrecord->outhiplane = (gfilterrecord->planes -1);
}
//这里对当前贴片进行处理,注意如果用户按了esc,下一次调用将是finish
void docontinue()
{
int index; //像素索引
if(gfilterrecord == null)
return;
//定位像素
int planes = gfilterrecord->outhiplane - gfilterrecord->outloplane + 1; //通道数量
//填充颜色
uint8 r = getrvalue(gparams.fillcolor);
uint8 g = getgvalue(gparams.fillcolor);
uint8 b = getbvalue(gparams.fillcolor);
int opacity = gparams.opacity;
uint8 *pdatain = (uint8*)gfilterrecord->indata;
uint8 *pdataout = (uint8*)gfilterrecord->outdata;
//扫描行宽度(字节)
int stride = gfilterrecord->outrowbytes;
//我们把输出矩形拷贝到 m_tile
copypsrect(&gfilterrecord->outrect, &m_tile);
for(int j = 0; j< (m_tile.bottom - m_tile.top); j++)
{
for(int i = 0; i< (m_tile.right - m_tile.left); i++)
{
index = i*planes + j*stride;
//为了简单明了,我们默认把图像当作rgb格式(实际上不应这样做)
pdataout[ index ] =
(uint8)((pdatain[ index ]*(100-opacity) + r*opacity)/100); //red
pdataout[ index+1 ] =
(uint8)((pdatain[ index+1 ]*(100-opacity) + g*opacity)/100); //green
pdataout[ index+2 ] =
(uint8)((pdatain[ index+2 ]*(100-opacity) + b*opacity)/100); //blue
}
}
//判断是否已经处理完毕
if(m_tile.right >= gfilterrecord->filterrect.right && m_tile.bottom >= gfilterrecord->filterrect.bottom)
{
//处理结束
zeropsrect(&gfilterrecord->inrect);
zeropsrect(&gfilterrecord->outrect);
zeropsrect(&gfilterrecord->maskrect);
return;
}
//设置下一个tile
if(m_tile.right < gfilterrecord->filterrect.right)
{
//向右移动一格
m_tile.left = m_tile.right;
m_tile.right = min(m_tile.right + tilesize, gfilterrecord->filterrect.right);
}
else
{
//向下换行并回到行首处
m_tile.left = gfilterrecord->filterrect.left;
m_tile.right = min(m_tile.left + tilesize, gfilterrecord->filterrect.right);
m_tile.top = m_tile.bottom;
m_tile.bottom = min(m_tile.bottom + tilesize, gfilterrecord->filterrect.bottom);
}
//zeropsrect(&gfilterrecord->inrect);
copypsrect(&m_tile, &gfilterrecord->inrect);//现在我们需要请求和outrect一样的区域
copypsrect(&m_tile, &gfilterrecord->outrect);
//请求全部通道(则数据为interleave分布)
gfilterrecord->inloplane = 0;
gfilterrecord->inhiplane = (gfilterrecord->planes -1);;
gfilterrecord->outloplane = 0;
gfilterrecord->outhiplane = (gfilterrecord->planes -1);
}
//处理结束,这里我们暂时什么也不需要做
void dofinish()
{
//清除需要显示ui的标志
m_showui = false;
//记录参数
writeparams();
}
(5)从ps scripting system中读写我们的参数,我们为项目添加 paramsscripting.h 和 paramsscripting.cpp,代码如下。引入readparams 和 writeparams 方法,该节主要涉及 ps 的描述符回调函数集,比较复杂,但在这里限于精力原因,我也不做更多解释了。具体可以参考我以前发布的相关随笔中有关讲解ps回调函数集的一篇文章以及代码注释。其相关代码如下:
#include "stdafx.h"
#include "paramsscripting.h"
#include <stdio.h>
oserr readparams(bool* showdialog)
{
oserr err = noerr;
pireaddescriptor token = null; //读操作符
descriptorkeyid key = null; //uint32,即char*,键名
descriptortypeid type = null;
int32 flags = 0;
int32 intvalue; //接收返回值
char text[128];
//需要读取的keys
descriptorkeyidarray keys = { key_fillcolor, key_opacity, null };
if (showdialog != null)
*showdialog = m_showui;
// for recording and playback 用于录制和播放动作
pidescriptorparameters* descparams = gfilterrecord->descriptorparameters;
if (descparams == null)
return err;
readdescriptorprocs* readprocs = gfilterrecord->descriptorparameters->readdescriptorprocs;
if (readprocs == null)
return err;
if (descparams->descriptor != null)
{
//打开描述符token
token = readprocs->openreaddescriptorproc(descparams->descriptor, keys);
if (token != null)
{
while(readprocs->getkeyproc(token, &key, &type, &flags) && !err)
{
switch (key)
{
case key_fillcolor: //读取填充颜色
err = readprocs->getintegerproc(token, &intvalue);
if (!err) gparams.fillcolor = intvalue;
break;
case key_opacity: //读取不透明度
err = readprocs->getintegerproc(token, &intvalue);
if (!err) gparams.opacity = intvalue;
break;
default:
err = readerr;
break;
}
}
//关闭描述符token
err = readprocs->closereaddescriptorproc(token);
//释放描述符
gfilterrecord->handleprocs->disposeproc(descparams->descriptor);
descparams->descriptor = null;
}
//播放动作时的选项,是否需要显示对话框
*showdialog = descparams->playinfo == plugindialogdisplay;
}
return err;
}
//写参数
oserr writeparams()
{
oserr err = noerr;
piwritedescriptor token = null;
pidescriptorhandle h;
pidescriptorparameters* descparams = gfilterrecord->descriptorparameters;
if (descparams == null)
return err;
writedescriptorprocs* writeprocs = gfilterrecord->descriptorparameters->writedescriptorprocs;
if (writeprocs == null)
return err;
//打开写描述符token
token = writeprocs->openwritedescriptorproc();
if (token != null)
{
//写入填充颜色
writeprocs->putintegerproc(token, key_fillcolor, (int32)gparams.fillcolor);
//写入不透明度
writeprocs->putintegerproc(token, key_opacity, (int32)gparams.opacity);
//释放描述符
gfilterrecord->handleprocs->disposeproc(descparams->descriptor);
//关闭token
writeprocs->closewritedescriptorproc(token, &h);
//恢复描述符
descparams->descriptor = h;
//录制选项
descparams->recordinfo = plugindialogoptional;
}
else
{
return errmissingparameter;
}
return err;
}
(6)这样我们就完整支持了参数读写,我们可以在执行滤镜时,点击“好”按钮,即可将参数更新到ps脚本系统,下次调用时会自动从脚本系统中读取上一次的参数值,并使用读取出的值初始化对话框。而当我们点击“最近滤镜”命令时,滤镜将会采用脚本系统中的参数,并且不显示对话框。
(7)下面是源代码的下载链接:
http://files.cnblogs.com/hoodlum1980/fillred.rar
【注意】为了节省空间,提供源码时,我将覆盖以前的项目版本,也就是说在原有基础上增量更新而不再保留历史版本。
(8)总结:
这一节主要讲解为滤镜引入自定义参数以及相关的对话框资源,然后为参数增加ps脚本系统的读写支持。
到目前为止,这个滤镜已经具有比较完整的框架了,也能够被动作录制和回放。
(a)但美中不足的是,对“动作录制和回放”的支持还不够完备,我们将看到当把滤镜录制为动作时,其对话框选项的勾选框是没有的,也就是我们没法设置对话框显示的“显示”,“不显示”,“安静”三种模式,这是因为我们还没有为滤镜引入必须的事件,描述符键等相关的“术语”(aete)资源。
(b)我们还希望为滤镜的对话框引入“预览”机制,即我们希望在对话框上显示一小块图片供用户预览效果,这样用户就可以根据视觉反馈方便的调节滤镜参数。
在此后,我们将有可能进一步讲解ps的回调函数集,例如如何让ps为我们申请内存,如何更新ps的进度条,如何更完善的处理用户交互,以及引入“预览”支持,引入aete资源等相关内容。
(9)最后更新:把rgb三个通道共用一个不透明度参数,调整为可以单独每个通道的合成不透明度。因此对话框做了相应修改。
更多怎样编写一个photoshop滤镜(2)。
