天天看點

對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按鈕,而是響應在任意位置的點選以及Enter鍵。

(二)濾鏡插件的介紹

濾鏡插件的作用是,針對圖像的選擇區域做出修改。濾鏡行為從調節飽和度,亮度,到對圖像的濾波等等。濾鏡在windows下的擴充名是“.8BF”。

下圖顯示了PS和濾鏡插件之間的調用序列,非常重要,這是在SDK文檔中的一個圖檔,對每一個類型的插件都有這樣一幅圖,這裡顯示的是濾鏡插件的調用序列。

對Photoshop第三方濾鏡插件開發的簡介

濾鏡可以使用濾鏡菜單進行調用,即最上面的調用起始點。在調用一次以後,Photoshop将把最近一次濾鏡操作放到濾鏡菜單的“最近一次濾鏡”子菜單上,以後點選該菜單則對應上圖中的“最近一次濾鏡指令”。下面我們将簡要介紹上圖流程。首先我們看一個濾鏡的入口點函數的“模闆”:

對Photoshop第三方濾鏡插件開發的簡介
對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();

  case filterSelectorPrepare:

   DoPrepare();

  case filterSelectorStart:

   DoStart();

  case filterSelectorContinue:

   DoContinue();

  case filterSelectorFinish:

   DoFinish();

  default:

   *gResult = filterBadParameters;

 }

}

注意,上面這個函數就是我們的濾鏡的最重要的一個函數,因為這個函數是提供給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調用是使用下面的循環來貼片處理圖像,直到整個圖像處理結束。

對Photoshop第三方濾鏡插件開發的簡介
對Photoshop第三方濾鏡插件開發的簡介

advanceState回調示例

for(

對Photoshop第三方濾鏡插件開發的簡介
對Photoshop第三方濾鏡插件開發的簡介

..)

{

    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

         我們使用上面的像素定位方式,可以很容易寫出下面的循環進行中的内容:

對Photoshop第三方濾鏡插件開發的簡介
對Photoshop第三方濾鏡插件開發的簡介

高斯模糊(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啟動時,它将掃描各插件目錄下的插件,并加載到相應菜單。

對Photoshop第三方濾鏡插件開發的簡介

       處理結果:

對Photoshop第三方濾鏡插件開發的簡介

       最後是這個濾鏡的一個壓縮包的下載下傳連結:

       安裝方法是,将檔案解壓,放到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許可協定。

//-------------------------------------------------------------------------------

繼續閱讀