在很久前我曾經寫過一篇文章簡要講述了photoshop的濾鏡開發的基本概念,並描述了濾鏡和PS之間的協作關係,也提供了一個雨滴效果濾鏡的Demo。但是缺少原始碼。而且我們將要產生疑問,我們該如何從頭開始編寫一個Photoshop濾鏡?我們如何建立一個最簡單的Ps濾鏡插件的基本框架,然後在這個基礎上繼續添加我們想要的功能呢?這裡,我就以回答一個網友向我提出的問題為例,從最基本的建立專案開始講起。這個例子(也是這個網友的問題)是,他想做一個最簡單的濾鏡,也就是只把影像填滿為「紅色」。對於PS使用者來說,這當然是非常簡單容易的事情,只需要一個快捷鍵操作而已,(濾鏡通常是用來完成比較複雜的任務的),我們就從這個最基本的例子出發講解編寫濾鏡的過程。在文章最後將附上範例的原始碼下載連結。
(1)我們使用的開發工具是Visual Studio .NET 2005版本,並搭配Photoshop SDK CS(本質上是一些C++程式碼和資源等檔案組成的發行包)。開發語言使用的是C和C++。
那麼使用C#或其他語言使用嗎?目前來看可行性不大。所以要開發 Photoshop 濾鏡,則要求開發者必須有較好的C 和 C++ 基礎,這是最重要的。當然如果開發者熟悉影像處理,數位訊號處理的基本知識將會更好。
(2)在準備工具後,並開啟VS2005,並新建專案。專案範本我們選擇 Visual C++ 的 Win32。專案名稱我們輸入我們想要建立的濾鏡名稱,例如「FillRed」濾鏡,表示這個濾鏡用於填紅色,如下圖:
點選確定以後,在彈出的設定對話框上,點擊“應用程式設定”,在應用程式類型中選擇選擇“DLL”,然後點擊確定。
名稱,在專案屬性中我們做以下設定:
(以a)為常規中,我喜歡把項目使用的字元集改為“使用多位元組字元集”,這使我們可以用char*以及直接使用雙引號的字串類型。當然也可以使用Unicode,但兩者的使用的字串函數會有所不同。可以隨你的喜歡設定。 ‧改為如下圖所示。
在右側下拉方塊中選取「包含檔案」:把幾個Photoshop SDK的資料夾加入選項的VC++包含目錄中,這將會方便我們編譯專案時不會報告找不到檔案的錯誤。如下圖所示:
原始檔“FillRed.cpp”,我們把該原始檔的程式碼替換為如下:
// FillRed.cpp : 定义 DLL 应用程序的入口点。 // #include "stdafx.h" #include "PiFilter.h" #include <stdio.h> #ifdef _MANAGED #pragma managed(push, off) #endif #ifndef DLLExport #define DLLExport extern "C" __declspec(dllexport) #endif //======================================= // 全局变量 //======================================= //dll instance HINSTANCE dllInstance; FilterRecord* gFilterRecord; int32* gData; int16* gResult; SPBasicSuite* sSPBasic = NULL; #define TILESIZE 128 //贴片大小:128 * 128 Rect m_Tile; //当前图像贴片(128*128) //======================================= // 函数列表 //======================================= //辅助函数,拷贝矩形 void CopyPsRect(Rect* src, Rect* dest); //辅助函数,把一个矩形置为空矩形 void ZeroPsRect(Rect* dest); void DoParameters(); void DoPrepare(); void DoStart(); void DoContinue(); void DoFinish(); //辅助函数,拷贝矩形 void CopyPsRect(Rect* src, Rect* dest) { dest->left = src->left; dest->top = src->top; dest->right = src->right; dest->bottom = src->bottom; } //辅助函数,把一个矩形置为空矩形 void ZeroPsRect(Rect* dest) { dest->left = 0; dest->top = 0; dest->right = 0; dest->bottom = 0; } //DLLMain BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { dllInstance = static_cast<HINSTANCE>(hModule); 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; if (selector == filterSelectorAbout) { //显示关于对话框 MessageBox(GetActiveWindow(), "FillRed Filter: 填充红色-- by hoodlum1980", "关于 FillRed", MB_OK); } else { gFilterRecord = (FilterRecordPtr)filterRecord; 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 DoParameters() { } //在此时告诉PS(宿主)滤镜需要的内存大小 void DoPrepare() { if(gFilterRecord != NULL) { gFilterRecord->bufferSpace = 0; gFilterRecord->maxSpace = 0; } } //inRect : 滤镜请求PS发送的矩形区域。 //outRect : 滤镜通知PS接收的矩形区域。 //filterRect : PS通知滤镜需要处理的矩形区域。 //由于我们是使用固定的红色进行填充,实际上我们不需要请求PS发送数据 //所以这里可以把inRect设置为NULL,则PS不向滤镜传递数据。 void DoStart() { if(gFilterRecord == NULL) 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->outRect); //请求全部通道(则数据为interleave分布) gFilterRecord->inLoPlane = 0; gFilterRecord->inHiPlane = 0; gFilterRecord->outLoPlane = 0; gFilterRecord->outHiPlane = (gFilterRecord->planes -1); } //这里对当前贴片进行处理,注意如果用户按了Esc,下一次调用将是Finish void DoContinue() { if(gFilterRecord == NULL) return; //定位像素 int planes = gFilterRecord->outHiPlane - gFilterRecord->outLoPlane + 1; //通道数量 uint8 *pData=(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++) { //为了简单明了,我们默认把图像当作RGB格式(实际上不应这样做) //pData[ i*planes + j*stride + 0 ] = 0; //Red //pData[ i*planes + j*stride + 1 ] = 0; //Green pData[ i*planes + j*stride + 2 ] = 255; //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->outRect); //请求全部通道(则数据为interleave分布) gFilterRecord->inLoPlane = 0; gFilterRecord->inHiPlane = 0; gFilterRecord->outLoPlane = 0; gFilterRecord->outHiPlane = (gFilterRecord->planes -1); } //处理结束,这里我们暂时什么也不需要做 void DoFinish() { }
= min(m_Tile.top + TILESIZE, gFilterRecord 設定inRect, outRect
ZeroPsRect (&gFilterRecord
->inRect); //我們不需要PS告訴我們原圖上是什麼顏色,因為我們只是填充 & m_Tile, &gFilterRecord
->outRect);; gFilterRecord->inLoPlane =
0; gFilterRecord->
inHiPlane ->inHiPlane gFilterRecord->outLoPlane =
0; gFilterRecord->outHiPlane = (gFilterRecord
-> (gFilterRecord //這裡對目前貼片進行處理,注意如果使用者按了Esc,下次呼叫將是Finish
void DoContinue(){ if{ ) return;
//
定位像素
int. >outHiPlane
-
gFilterRecord->outLoPlane +
1;
//通道數 =
(uint8*)gFilterRecord->outData; //掃描行寬度(位元組)
int stride owBytes; //我們把輸出矩形拷貝到 m_Tile
CopyPsRect(&gFilterRecord
for(int j = 0
; j< (m_Tile.bottom - m_Tile.top++ for(int i = 0; i<
(m_Tile.right
{ //為了簡單明了,我們預設把影像當作RGB格式(實際上不應該這樣做) //pData[ i*planes + j*stride Red / /pData[ i*planes + j*stride + 1 ] = 0; //Green i*planes + j
* stride +
2 ] =
} } //
//完畢 if(m_Tile.right >= gFilterRecord-Tile.R >= gFilterRecord->filterRect. bottom) { //
處理結束 cord->
inRect); ZeroPsRect(&gFilterRecord--&
gFilterRecord-- outRect); ZeroPsRect(&gFilterRecord
-> } //設定下一個tile
if (m_Tile.right <
gFilterRecord->filterRect.right) m_Tile.left = m_Tile. right;
m_Tile.right
= min(m_Tile.right +
.)5,0,03LE-SI } else
/向下換行並回到行首處 m_Tile.left =fil. m_Tile.right =
min(m_Tile.left
+ TILESIZE, gFilterRecord
->
filterRect.right); m_Tile.bottom = min(m_Tile.bottom
+ TILESIZE, gFilterRecord->filterRect.bottom); } inRect);
CopyPsRect(&m_Tile, &gFilterRecord->outRect);請全部要求
gFilterRecord- >
inLoPlane = 0; gFilterRecord-> gFilterRecord ;
gFilterRecord->outLoPlane = 0;
gFilterRecord->outHiPlane = (g );}
//處理結束,這裡我們暫時什麼也不需要做void DoFinish(){} 上面的程式碼也就是濾鏡的一個基本框架,我們可以看到DLL將提供的一個導出函數是PluginMain函數,我們將把圖像用128*128像素的切片進行分割處理,這可以使PS和濾鏡之間每次傳遞較少量數據,尤其是對很大的圖像來說,切片處理將有利於應對記憶體緊張的情況,也是Photoshop所提倡的,64*64或128*128是比較典型的尺寸。 ¢後數據,至於inRect,由於我們僅僅是填充,所以我們不關心圖像的原來顏色如何,所以inRect 可以設定為「空矩形」。影像的通道這裡我們為了程式碼的直覺和簡單起見,我們就只考慮RGB影像,也就是有3個通道的影像。設定好第一個 outRect,然後PS就會依序不停的開始進行 continue 調用,貼片從左到右,從上到下的順序拼貼,直到貼片全部處理完成。注意inData 和outData 是PS 向濾鏡提供的申請資料和“回寫緩衝區”,一個用於讀取,一個用於寫,它們的大小是由濾鏡向PS通知請求時填寫的資料控制的,操作時絕不能越出其邊界。
可能
有關此濾鏡主體程式碼詳細請參考程式碼註釋,以及我先前文章的解釋。以及PS SDK的官方文件。這裡我們就不再細述程式碼的原理了,因為它是恨簡單的。上面的一切程式碼裡面需要引用ps sdk中的頭檔。
(4)在此期間,以已成功完成專案以完成專案。但下面一步是欠入 PIPL 資源。
且可插入 PIPL 資源,以Photoshop正確辨識並負荷。根據PS提供的文件介紹,PIPL的發音讀作"pipple",它表示 Plug-In Property List。它是一個靈活,可擴展的用於表示插件模組元資料(metadata)的資料結構。 pipl包含了Photoshop識別和載入插件模組的所有訊息,包含一些標記,以及控制每個插件的各種靜態屬性等等,你的濾鏡可以包含一個或多個「pipl」結構。
為濾鏡插入pipl資源的過程如下,而首先我們需要為專案新增一個*.r(Macintosh的Rez檔案)文件,然後使用cl.exe 將這個檔案編譯為一個檔案最後用Ps SDK提供的一個資源轉換工具CnvtPipl.Exe 把*.rr 文件轉換為*.pipl 文件,然後為濾鏡添加一個*.rc資源文件,在rc文件的末尾把pipl文件包含進來即可。
ps sdk已經為我們提供了一個通用的 r文件,包括了一般的屬性定義,而它是 PIGeneral.r 檔。
(a)在「「FillRed.r」上新增一個「FillRed.r」資料。雙擊開啟該文件,複製以下內容:
// ADOBE SYSTEMS INCORPORATED // Copyright 1993 - 2002 Adobe Systems Incorporated // All Rights Reserved // // 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. //------------------------------------------------------------------------------- #define plugInName "FillRed Filter" #define plugInCopyrightYear "2009" #define plugInDescription \ "FillRed Filter.\n\t - http:\\www.cnblogs.com\hoodlum1980" #include "E:\Codes\Adobe Photoshop CS2 SDK\samplecode\common\includes\PIDefines.h" #ifdef __PIMac__ #include "Types.r" #include "SysTypes.r" #include "PIGeneral.r" #include "PIUtilities.r" #include "DialogUtilities.r" #elif defined(__PIWin__) #define Rez #include "PIGeneral.h" #include "E:\Codes\Adobe Photoshop CS2 SDK\samplecode\common\resources\PIUtilities.r" #include "E:\Codes\Adobe Photoshop CS2 SDK\samplecode\common\resources\WinDialogUtils.r" #endif resource 'PiPL' ( 16000, "FillRed", purgeable ) { { Kind { Filter }, Name { plugInName }, Category { "Demo By hoodlum1980" }, Version { (latestFilterVersion << 16) | latestFilterSubVersion }, #ifdef __PIWin__ CodeWin32X86 { "PluginMain" }, #else CodeMachOPowerPC { 0, 0, "PluginMain" }, #endif SupportedModes { noBitmap, doesSupportGrayScale, noIndexedColor, doesSupportRGBColor, doesSupportCMYKColor, doesSupportHSLColor, doesSupportHSBColor, doesSupportMultichannel, doesSupportDuotone, doesSupportLABColor }, EnableInfo { "in (PSHOP_ImageMode, GrayScaleMode, RGBMode," "CMYKMode, HSLMode, HSBMode, MultichannelMode," "DuotoneMode, LabMode," "Gray16Mode, RGB48Mode, CMYK64Mode, Lab48Mode)" }, PlugInMaxSize { 2000000, 2000000 }, FilterCaseInfo { { /* array: 7 elements */ /* Flat data, no selection */ inStraightData, outStraightData, doNotWriteOutsideSelection, doesNotFilterLayerMasks, doesNotWorkWithBlankData, copySourceToDestination, /* Flat data with selection */ inStraightData, outStraightData, doNotWriteOutsideSelection, doesNotFilterLayerMasks, doesNotWorkWithBlankData, copySourceToDestination, /* Floating selection */ inStraightData, outStraightData, doNotWriteOutsideSelection, doesNotFilterLayerMasks, doesNotWorkWithBlankData, copySourceToDestination, /* Editable transparency, no selection */ inStraightData, outStraightData, doNotWriteOutsideSelection, doesNotFilterLayerMasks, doesNotWorkWithBlankData, copySourceToDestination, /* Editable transparency, with selection */ inStraightData, outStraightData, doNotWriteOutsideSelection, doesNotFilterLayerMasks, doesNotWorkWithBlankData, copySourceToDestination, /* Preserved transparency, no selection */ inStraightData, outStraightData, doNotWriteOutsideSelection, doesNotFilterLayerMasks, doesNotWorkWithBlankData, copySourceToDestination, /* Preserved transparency, with selection */ inStraightData, outStraightData, doNotWriteOutsideSelection, doesNotFilterLayerMasks, doesNotWorkWithBlankData, copySourceToDestination } } } };
第二行表示使用PS SDK中的 Cnvtpipl.exe 工具把 rr文件编译为 pipl文件,请注意为了简单,我把该工具复制到了项目的源文件所在文件夹下面。它位于SDK的路径是:“\samplecode\resources\cnvtpipl.exe”。
(b)下面我们为项目添加一个 rc文件,同样右键点击“资源文件”,添加一个FillRed.rc文件。
这是一个windows的资源文件,我们暂时还不需要任何资源,所以我们直接用文本方式打开IDE自动生成的RC文件,在结尾处添加下面的一行:
// #endif // APSTUDIO_INVOKED #endif // 英语(美国)资源 ///////////////////////////////////////////////////////////////////////////// #ifndef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // 从 TEXTINCLUDE 3 资源生成。 // #include "FillRed.pipl" ///////////////////////////////////////////////////////////////////////////// #endif // 不是 APSTUDIO_INVOKED
(5)我们编译项目,即可在项目的输出目录中看到 生成 FillRed.8bf 文件,下面我们把这个文件复制到 Photoshop的滤镜文件夹下面,例如我的Photoshop CS的滤镜所在目录是:“D:\Program Files\Adobe\Photoshop CS\增效工具\滤镜”
最后我们启动Photoshop,Photoshop会扫描插件目录,并把我们的滤镜加载到相应的菜单上,我们选择一个矩形选区,然后点击我们制作的滤镜相应菜单,即可看到效果,如下图所示。注意,下面的例子的效果是我仅仅把蓝通道填充了255。
在Photoshop 的說明選單- 關於增效工具- 的子選單中,可看到我們所寫的「FillRed Filter ...」一項,點選它時PS即發起about 調用,即可看到彈出about 調用的MessageBox。
(6)最後,或有這個小例子的原始碼下載 - 例如PhotoshopSDK的目錄等,需要依據具體環境做出相對調整。 PS SDK提供的資源轉換工具也包含在專案資料夾內。 (註:附件中未包含完整PS SDK)
http://files.cnblogs.com/hoodlum1980/FillRed.rar 以在這一節中敘述了從專案創建,到嵌入pipl資源,並建立了一個基本的濾鏡架構。但它的功能是非常基本和簡單的,在以後的時間裡,我們可能需要繼續豐富這個例子,包括為它引入對話框資源,令PS為我們的濾鏡緩存和讀取我們的參數,包括在對話方塊表面繪製濾鏡的預覽圖形等等。更多怎麼寫一個Photoshop濾鏡(1)相關文章請追蹤PHP中文網!