當前位置:編程學習大全網 - 源碼下載 - 怎樣編寫壹個Photoshop濾鏡(2)

怎樣編寫壹個Photoshop濾鏡(2)

在上壹篇文章中,我們講解了怎樣創建壹個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)

{

  • 上一篇:我的電腦怎麽玩不了QQ麻將?(打開QQ麻將客戶端失敗請嘗試重新安裝此遊戲)
  • 下一篇:Activity的基礎知識(下)
  • copyright 2024編程學習大全網