对Photoshop第三方滤镜插件开发的简介

简介: Photoshop是数字图像处理领域内的杰出软件。同时,它也允许第三方以插件(Plugin) 的形式扩展其功能。Photoshop的插件目前一共可分为以下九种:自动化(批处理)(出现在‘自动’子菜单下),颜色拾取,导入,导出(出现在‘导入’‘导出’子菜单下),扩展,滤镜,文件格式(出现在打开,存储为),解析(与导出导出功能),选取(出现在‘选择’菜单下)。
       Photoshop是数字图像处理领域内的杰出软件。同时,它也允许第三方以插件(Plugin) 的形式扩展其功能。Photoshop的插件目前一共可分为以下九种:自动化(批处理)(出现在‘自动’子菜单下),颜色拾取,导入,导出(出现在‘导入’‘导出’子菜单下),扩展,滤镜,文件格式(出现在打开,存储为),解析(与导出导出功能),选取(出现在‘选择’菜单下)。这里我们以最为用户熟悉的滤镜为例讲解。
(一)插件的通用部分介绍:
        调用插件的主程序我们成为宿主,在大多数情况下就是Photoshop(以下简称PS),一个插件实际上在windows系统下是一个动态链接库(只是具有不同扩展名)。PS使用LoadLibray加载插件模块。当用户采取相应操作时,将引起一系列ps对插件模块的调用。所有的这些调用都是调用同一个入口点。该入口点是这样一个函数,定义如下:(由于PS采取对windows和苹果机兼容,这里我们只给出在windows系统上的定义)

void ENTRYPOINT (
         short selector,
         void* pluginParamBlock,
         long* pluginData,
         short* result);

selector:
      操作类型指示符。当seletor=0时,它对所有类型的插件都具有相同的意义,即要求显示一个关于对话框。其他值,则根据插件类型而意义有所不同。

pluginParamBlock:
      这是一个指向一个很大结构的指针,这个结构用于在宿主和插件之间传递信息和数据。对于不同类型插件,它具有不同结构。

pluginData:
       一个指向int32类型的指针。它是为PS为插件在多次调用期间保存的一个值。它的一个标准用法是插件可把一些全局数据的指针交给该参数保存,

result:
       一个指向int16的指针,每次插件被调用它必须设置result。返回0表示插件代码中没有发生错误。当发生错误时,这个值返回一个错误码。有关错误码,ps为不同类型的插件分别划分了错误码的范围,并在SDK中预定义了一些值。

关于对话框:
所有插件应该响应about调用。插件可以显示一个自定义的对话框。但是为了保持一致性,应该遵守下面的约定:
(1)显示在主屏幕的水平居中,垂直1/3高度处。
(2)不需要包含一个OK按钮,而是响应在任意位置的点击以及回车键。

(二)滤镜插件的介绍
滤镜插件的作用是,针对图像的选择区域做出修改。滤镜行为从调节饱和度,亮度,到对图像的滤波等等。滤镜在windows下的扩展名是“.8BF”。
下图显示了PS和滤镜插件之间的调用序列,非常重要,这是在SDK文档中的一个图片,对每一个类型的插件都有这样一幅图,这里显示的是滤镜插件的调用序列。
              

滤镜可以使用滤镜菜单进行调用,即最上面的调用起始点。在调用一次以后,Photoshop将把最近一次滤镜操作放到滤镜菜单的“最近一次滤镜”子菜单上,以后点击该菜单则对应上图中的“最近一次滤镜命令”。下面我们将简要介绍上图流程。首先我们看一个滤镜的入口点函数的“模板”:
EntryPoint Of Plugin :PlugInMain
// Create a definition for exported functions
#define DLLExport extern "C" __declspec(dllexport)
#define SPAPI

DLLExport SPAPI 
void PluginMain(const int16 selector,
        
void * filterRecord,
        int32 
* data,
        int16 
* result)

 
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;
 }
}
注意,上面这个函数就是我们的滤镜的最重要的一个函数,因为这个函数是提供给PS调用的,我们可以看到这个函数声明为了一个Dll导出函数。从调用序列可以看到,这个机制使得这个函数的作用和 窗口的窗口过程 非常类似。窗口过程用于根据MSG ID处理消息,而这个函数主要用于根据selector做相应操作。因此他们都是有包含一个switch-case分支处理结构。

filterRecord
上面这个函数中用到的第二个参数,当属于about调用时(即selector=0)它是一个指向AboutRecord结构的指针,当非about调用时,他是一个指向FilterRecord结构的指针,FilterRecord结构是一个非常庞大复杂的结构,它是ps和滤镜之间用于沟通和传递数据的关键载体,它的sizeof=452个字节,大约包含100多个成员,在文档中一共有7页用于介绍该结构的成员含义。FilterRecord的完整定义位于头文件:sdk中的pifilter.h。下面我将在提到时再去讲解它最基本和最重要的一些成员。

(三)调用流程简介。

(3.1)filterSelectorParameters调用:
如果滤镜有一些参数需要用户设置,那么它应该把参数保存到一个位置。然后把该地址设置到第三个参数data。PS将把这个参数初始化为NULL。这个调用是否发生取决于用户的调用方式,当一个滤镜刚刚调用后,这个滤镜将出现在滤镜的最近一次命令菜单,用户可以以此菜单以相同参数(这时不会显示对话框要求用户设置新的参数)再次调用。当用户使用最后一次滤镜命令调用时,该调用不会发生。(参加上图)。因此,如果错误的参数可能产生使程序崩溃的危险时,应该每一次都检查,验证,然后初始化参数。
      注意!:因为对对不同大小的图像可以用同样的参数,所以参数不应该依赖图像大小。例如,某个参数不应该取决于图像宽度或者高度,通常应该用一个百分比或者比例系数作为参数比较合适。
      所以,你的参数数据块应该包含以下信息:
       1. 一个签名,这样滤镜可以快速的确认这是属于它的参数数据。
       2.一个版本号,这样插件可以自由升级而无需改变签名。
       3.字节序标识。(为了跨平台)指示当前使用的是什么字节序。

      Parameter block (参数数据块)和scripting system (脚本描述系统)
      脚本描述系统用于缓存我们的参数,在每种调用类型都会把它传递给插件,因此可以使用它存储你的所有参数。一旦你的Parameter block通过验证,你都应该从传递的参数读取数据,然后更新你的参数。例如:
       1.首先调用ValidateMyParameters验证或者初始化你的全局参数。
       2.然后调用ReadScriptingParameters方法读取参数,并写入你的全局参数数据结构。

(3.2)filterSelectorPrepare调用:
        该调用允许你的插件模块调节ps的内存分配算法。“最近一次滤镜”命令将从这个调用开始。PS将maxSpace(这是属于FilterRecord结构(第二个参数)的成员,此后出现的新成员将不再做特殊说明)设置为他能为插件分配的最大字节数。
         imageSize, planes 以及 filterRect 成员:
         这几个成员现在(指sdk 6.0)已经定义,可以用于计算你的内存需求量。imageSize,图像尺寸。planes,通道数。
         filterRect:滤镜矩形。
         这里我再强调一下这个filterRect,它是PS定义的Rect类型(和windows api的RECT结构类似)。这个概念也就是我在《置换滤镜原理》的研究一贴中提到的和反复强调的“选区外接矩形”的概念,在当时我还没有接触到ps sdk。这里我们看到,在Photoshop的代码中,它被称为filterRect。
         bufferSpace:
         如果滤镜想分配超过32K的空间,那么应该设置这个成员为你想申请的字节数。ps会尝试在下一个调用(start调用)前释放这样大小的空间,以保证你的下次调用成功。

(3.3)filterSelectorStart调用:
          该调用中,应该验证参数数据块,根据ps传递来的参数,去更新你自己参数,如果需要显示你的UI。然后进入你的数据处理流程。
          advanceState 回调:(用于请求PS更新相应的数据)
          这是一个PS提供给滤镜的非常重要的回调函数,它的定义如下:
           #define MACPASCAL
           typedef short OSErr;
           typedef MACPASCAL OSErr (*AdvanceStateProc) (void);
          他的作用是要求PS立即更新FilterRecord中的过时数据。例如我们可以设置我们新的处理矩形,然后调用该函数,就可以在此调用后得到我们需要的新的数据。如果你使用这个回调,那么你的核心处理可以全部在start调用中完成,而无需使用continue调用。当处理完成后,可以设inRect=outRect=maskRect=NULL.
如果你不使用这个回调,那么你应该设置第一个矩形区域,然后使用continue调用循环处理。
        例如我们可以在start调用是使用下面的循环来贴片处理图像,直到整个图像处理结束。
advanceState回调示例
for(..)
{
    SetOutRect(out_recc);
    gFilterRecord
->outLoPlane=0;
    gFilterRecord
->outHiPlane=(g_Planes-1);

        
//请求PS更新数据!
    *gResult = gFilterRecord->advanceState();
    
if (*gResult != kNoErr) 
        
goto done;

   
//处理数据
    。。。。。
}
          
         inRect, outRect & maskRect
         设置inRect 和 outRect (当使用选区蒙版时设置maskRect),以请求第一个处理区域。如果可能,你应该把图片切成小片,这样可以减少传递数据时的内存需求量。使用 64x64 或者128x128 的贴片是一个比较习惯性的做法。

(3.4)filterSelectorContinue调用:
        当inRect, outRect,  maskRect之中的任一个不是空矩形时,该调用会持续发生。
         inData, outData & maskData
         这三个成员都是void *指针,指向你请求的图像数据起始点,我们最主要的工作就是计算,然后设置这里的数据,在调用时,inData和outData会根据你请求的矩形区域被相应位置的图像数据填充,然后你的处理任务主要是修改outData数据区,告诉PS你的处理结果,PS会把结果更新到图像。注意,你不需要考虑传递进来的选区和选区形状,因为这个工作PS会自动保护选区外的数据,你只需要关注你自己的算法即可。处理结束后,设置下一次处理的inRect,outRect。如果结束了,那么把它们置空即可。注意,inRect不一定和outRect是一样的。
         progressProc 回调:(用于设置PS进度条)
         它的类型定义为:  
         typedef MACPASCAL void (*ProgressProc) (int32 done, int32 total);

         这是PS提供的用于设置进度条的回调函数,插件可以使用这个回调让PS更新下面的进度条,它接收两个参数,一个是已经完成的任务数,一个是总任务数。当你需要想用户反馈你的处理进度,则可以这样设置进度:
         gFilterRecord->progressProc ( progress_complete,   progress_total );

         abortProc 回调:(用于向宿主查询用户是否采取了取消行为)
         其类型定义为:
         typedef  Boolean (*TestAbortProc) (void);
         这个回调用于查询用户是否取消了操作,在一个消耗时间较长的处理中,插件应该每秒钟调用几次这个函数,确认用户是否做了取消(例如按下Esc键等)。如果返回TRUE说明当前的操作应该取消,并且返回一个正的错误码。
 

(3.5)filterSelectorFinish调用:
        该调用允许插件在处理结束后做清理工作。当并且仅当start调用成功(没有返回错误)时,finish才会被调用。即使continue中发生错误,finish调用依然会发生。
        注意!:小心处理用户在continue调用期间取消的行为,通常这时候你在期待下一次continue调用。如果用户取消,下一次调用将是finish调用,而不是continue!!!。
        规则:如果start调用成功,Photoshop保证将会发起Finish调用。

(3.6)错误码
        插件可以返回标准的操作系统错误码,或者报告它自己的错误(正整数)。在sdk中有定义:
       #define filterBadParameters –30100 // 
       #define filterBadMode –30101 // 不支持该模式图片

(四)雨滴滤镜的DEMO
         前面我们主要讲述了一个滤镜的基础知识。然后这还是仅仅提取了最重要的部分讲解的,更多的技术细节限于篇幅无法估计。上文的主要基础是PS SDK6.0的文档,其中的主要规则属于对原文的翻译,有少量内容属于我个人的实践和理解(我将在有时间的时候把属于个人理解的用颜色区分注明)。
         现在我们才开始进入demo的讲解!!!真的很累。。。。
         雨滴滤镜的算法主要参考了国外的一个网址,这是一个网友给我的另一篇文章的回复中报告给我的地址。这个滤镜的起源是球形化算法(在PS中有此内置滤镜)。算法我们不做介绍了,因为它虽然是滤镜的核心,但不是本文重点。我们给出在此基础上水滴效果的伪码:
      
          ---水滴效果--------------------
          随机产生一个位置cx,cy,随机产生一个半径R,
          在cx,cy处以R为半径做球形化扭曲。
          在该位置处加上水滴的高光和阴影。
          对水滴内部做一个3*3模板的高斯模糊。
         ----------------------------------

 (4.1)像素定位
         重复上述过程,将产生多个水滴。这就是这个滤镜的核心算法。具体公式不给出了。但是为了处理数据,我们必须了解如何在PS传递来的数据中定位一个像素数据,例如我们想获得原图上(x,y)位置上R通道的像素数据,我们如何拿到呢?这里还要介绍FilterRecord中和像素定位相关的重要数据成员,
         int32 inRowBytes ,outRowBytes, maskRowBytes,
         这是相应的inData,outData,maskData中的扫描行宽度,都是int32类型,属于PS提供给插件的数据。在c#中相当于BitmapData.Stride。但是注意的是,在inData和outData中,数据未必是按照4byte对齐的!但是ps也没有说行尾就没有任何冗余字节。总之,它是一行图像数据在内存中的占据的字节数量(跨度)。
          int16 inLoPlane, inHiPlane, outLoPlane, outHiPlane,
          属于插件像PS请求时通知给PS的数据,值得是下一个处理中请求的第一个通道和最后一个通道值,注意这个值是以0为base的索引值。例如对于RGB图像来讲,有三个通道,0到2分别对应B,G,R(注意这里保持文件中顺序,而不是PS中的习惯的RGB顺序!!!)。我们可以一次请求一个通道,也可以一次请求多个通道,多个通道的数据将会依次交叉排布(interleave)。例如,如果我们设置inLoPlane=0,inHiPlane=2,则PS提供给我们的inData数据排列是:
          [B G R] [B G R] ... ....
          如果我们把inLoPlane=inHiPlane=1,则PS提供给我们的inData是:
          [G] [G] ......
                   
          好了有了上面的几个关键成员讲解,我们可以看到我们如何定位一个像素,其方式如下:
         首先我们请求的通道数量设为planes:则:
          planes=inHiPlane-inLoPlane+1; //通道数量         
          uint8 *pixels=(uint8*)inData;
          我们取到(x,y)位置的索引为k的通道数据表达式如下:
          pixels [ y * inRowBytes + x * planes + k ];
          或者
          *(pixels + y * inRowBytes + x * planes + k);
          
          例如,我们请求了一副图片的RGB三个通道(inLoPlane=0,inHiPlane=2),为简便,inRect设置为整个图片大小,则位于(x,y)位置的像素数据如下: 

           pixels [ y * inRowBytes + x * 3  ];          // p(x,y).B
           pixels [ y * inRowBytes + x * 3  + 1 ];    // p(x,y).G
           pixels [ y * inRowBytes + x * 3  + 2 ];    // p(x,y).R

         好了,有了上面的基础,我们可以看下面的高斯3*3模板处理,高斯3*3模板如下:
         1 2 1
         2 4 2  /16
         1 2 1
         我们使用上面的像素定位方式,可以很容易写出下面的循环处理中的内容:
高斯模糊(3*3模板)
sum=0;
//依次处理每个通道
for(k=0;k<g_Planes;k++)
{
    
//blur it!
    sum+=bufferPixels[(j-1)*rowBytes+(i-1)*g_Planes +k];
    sum
+=bufferPixels[(j-1)*rowBytes+(i)*g_Planes +k]*2;
    sum
+=bufferPixels[(j-1)*rowBytes+(i+1)*g_Planes +k];
    sum
+=bufferPixels[j*rowBytes+(i-1)*g_Planes +k]*2;
    sum
+=bufferPixels[j*rowBytes+i*g_Planes +k]*4;
    sum
+=bufferPixels[j*rowBytes+(i+1)*g_Planes +k]*2;
    sum
+=bufferPixels[(j+1)*rowBytes+(i-1)*g_Planes +k];
    sum
+=bufferPixels[(j+1)*rowBytes+(i)*g_Planes +k]*2;
    sum
+=bufferPixels[(j+1)*rowBytes+(i+1)*g_Planes +k];
    sum
=sum>>4;//即除以16
    pixels[j*rowBytes+(i)*g_Planes +k]=sum;
}


(五)结束语:
      最后,让我们看一下滤镜使用中的效果截图:在PS启动时,它将扫描各插件目录下的插件,并加载到相应菜单。
      
       处理结果:
       
       最后是这个滤镜的一个压缩包的下载链接:
       RainDropFilter.rar
       安装方法是,将文件解压,放到Photoshop的滤镜安装目录即可,例如对于Photoshop CS,它的滤镜安装目录可能形如:
       “C:\Program Files\Adobe\Photoshop CS\增效工具\滤镜\”
       有关PS SDK,可以从Adobe官方获取,目前是否是免费的我不清楚了。。。。。


(六)参考资料:
(1)Photoshop SDK 6.0。
(2)Photoshop SDK CS。
(3)(雨滴滤镜的算法)Filter: Raindrops :http://www.jasonwaltman.com/thesis/filter-raindrops.html

----------------------------------------------------------------------------
 附录:Adobe SDK的声明!
----------------------------------------------------------------------------
// ADOBE SYSTEMS INCORPORATED
// Copyright  1993 - 2002 Adobe Systems Incorporated
// All Rights Reserved
//
// ADOBE系统 公司
// 版权 1993 - 2002 Adobe公司
// 保留所有权利。
//
// NOTICE:  Adobe permits you to use, modify, and distribute this
// file in accordance with the terms of the Adobe license agreement
// accompanying it.  If you have received this file from a source
// other than Adobe, then your use, modification, or distribution
// of it requires the prior written permission of Adobe.
//
// 注意:Adobe允许你在遵守相应Adobe许可协议条款的条件下,使用,修改,和分发这个文件。
// 如果你从非Adobe方获得该文件,则你使用,修改和分发需要此前签署的Adobe许可协议。
//-------------------------------------------------------------------------------

我的相关文章:《怎样编写一个Photoshop滤镜(1)》

目录
相关文章
|
JavaScript 前端开发 程序员
用Unity不会几个插件怎么能行?Unity各类插件及教程推荐
话说工欲善其事必先利其器,程序员总是有一些开发利器,而对于Unity3D开发程序员来说,插件就是非常好用的利器。 今天博主,就将比较好用的插件推荐给大家,希望一起学习品鉴。
|
2月前
Threejs用官方提供的编辑器做一个简单的模型
这篇文章介绍了如何使用Three.js内置的编辑器来创建和编辑简单的3D模型,并提供了相应的操作指南。
222 0
|
6月前
|
数据可视化 大数据 API
【推荐100个unity插件之22】基于UGUI的功能强大的简单易用的Unity数据可视化图表插件——XCharts3.0插件的使用
【推荐100个unity插件之22】基于UGUI的功能强大的简单易用的Unity数据可视化图表插件——XCharts3.0插件的使用
202 0
|
6月前
|
存储 图形学
【推荐100个unity插件之13】推荐一款开源的Unity网格破碎插件,实现在Unity中展示可破坏的墙壁的——unity-fracture
【推荐100个unity插件之13】推荐一款开源的Unity网格破碎插件,实现在Unity中展示可破坏的墙壁的——unity-fracture
100 0
|
存储 JSON JavaScript
fabric.js开发图片编辑器可以实现哪些功能?多图
使用fabric.js开发了图片编辑器,用文字加动图的形式直观的分享出来,帮助做功能参考,项目已开源。
fabric.js开发图片编辑器可以实现哪些功能?多图
|
JSON JavaScript 前端开发
fabric.js开发图片编辑器的细节实现
如何用vue和fabric.js快速开发一款编辑器,并利用fabric.js的api提供标准编辑器的能力。
fabric.js开发图片编辑器的细节实现
|
数据可视化 vr&ar 图形学
Unity可视化编程XDreamer插件导入
前言 XDreamer是一款基于Unity平台开发的,可在Unity(包括编辑器与运行时)中使用的可扩展的中文交互编辑软件,可进行2D、3D、VR、AR、MR开发。 本期博客为XDreamer的官方讲解的学习记录。可以理解为UE4中的蓝图效果。是从事美术人员的福音,美术人员也可不用编写程序进行游戏的制作。 一、下载XDreamer官方插件包 XDreamer中文交互编辑器http://www.xdreamer.com.cn/请在官网进行下载,得到如下的文件。 二、插件加载 目前我导入到URP
612 0
Unity可视化编程XDreamer插件导入
|
存储 JSON 缓存
从零开发一款图片编辑器Mitu
我们知道,为了提高企业研发效能和对客户需求的快速响应,现在很多企业都在着手数字化转型,不仅仅是大厂(阿里,字节,腾讯,百度)在做低代码可视化这一块,很多中小企业也在做,拥有可视化低代码相关技术背景的程序员也越来受重视。
582 0
|
前端开发
Sketch插件新利器——使用摹客设计规范制作设计
Sketch,作为一款专为图标和界面设计而打造的优质矢量绘图工具,也是设计师们制作和完善公司企业内部设计规范系统不可或缺的设计工具。 然而,逐个导出和上传Sketch编辑优化的设计系统资源费时而费力。
2165 0