在上一篇文章裡,我們解釋了為濾鏡添加術語資源,從而使我們的濾鏡可以被PS的scripting system感知和描述,這樣即友好支持了PS的「動作」面板。在這篇文章中,我們將對先前的DEMO進行進一步的細化,例如在參數對話框上增加即時預覽的小縮圖等。對話框的引入主要是給使用者一個機會和接口,設定或調節濾鏡所使用的影像處理演算法。通常作為UI的友善性,在對話方塊上應該提供預覽圖,這樣可以直覺的把參數對結果產生的影響回饋給用戶,指導他們調整參數。而不是要使用者必須重複執行濾鏡指令才能看到效果然後去調節參數。
之前我覺得「加入縮圖」這樣的功能應該不是很困難,但當我嘗試這樣去做,我很快發現它的難度遠遠超過了以往我寫的文章中的講解。因為當我們嘗試使用PS提供的回調函數去顯示縮圖時,我們必須對PS提供的介面細節完全清楚,包括影響縮放的參數設置,資料分佈,掃描行等細節。不能夠有一分一毫的差錯,否則我們就可能看到不正常的顯示,甚至會不小心的使記憶越界。
在引入縮圖之前,且我先對濾鏡演算法做一些有趣的改進,並進行了一點增強,使它更加實用化。
(1)導入「像素隨機抖動」參數與演算法。
在先前設定像素時,輸入與輸出的位置原本是完全一致的,即Dest(i, j) = f (Src(i, j))。
現在我們考慮對上式進行一點改變,將來源像素進行隨機抖動,即Dest(i, j) = f (Src(i+dx, j+dy))。
我們設定抖動距離為distance(像素)參數,這樣我們取源像素時,以目前像素為中心向外圍擴展distance的正方形內隨機選取某點作為源像素。從而使他們在結果圖中具有一種“溶解”或“腐蝕”效果。如下圖所示:
因此我們在中音距參數中增加了distance參數,並表示上方圖中的隨機抖動距離參數。這樣當我們設定位於(i,j)位置的像素時,我們取的來源像素座標是:
x = i + rand()%(2*distance+1) - distance;
y = j + rand()%(2*distance+1) - distance;
## 把x,y限定在filterRect 內部。
而因我們所使用的是使用貼片(Tile)處理方式,因此我們的調整需要一點技巧性。我們將改變我們向PS請求的inRect,即每次貼片時,outRect依然保持和此前一致,而把inRect 嘗試向四周擴張distance像素距離,這樣可以保證我們每次貼片時都能拿到有效數據(當貼片位於filterRect內部時),除非貼片位於filterRect的邊緣。
必須注意的是,由於inRect 比outRect 要“大一圈”,所以這時候兩個Rect的像素已經不是大小一致完全重疊關係了,而是有一定重疊關係了,而是有一定重疊關係 移
的!在程式碼中我們必須考慮兩個矩形之間的偏移關係。這裡可以參考我的原始碼,就不詳細講解該處理方法了。
(2)為對話方塊增加「縮圖」(Proxy)。
#######
我們增加了一個參數,然後在對話方塊左側空出一個較小區域用於顯示縮圖,為了方便,我在縮圖位置放置了一個隱藏的STATIC 控制項(Proxy Banner),它的主要用處是使我可以在運行時獲取到“縮圖”的邊界(客戶區坐標)。修改後的對話方塊如下圖:
為
‧
typedef MACPASCAL OSErr (*DisplayPixelsProc) (
const VRect *srcRect,
int32 dstCol, void * platformContext);
第一個參數是一個PSPixelMap 結構體的指標用以描述一塊像素資料區,它相當於BitBlt中的來源圖,srcRect參數描述的是來源描述一塊像素資料區,它相當於BitBltect參數描述的是來源圖矩形;
dstRow 和dstCol 所描述的是目標區域的「目標行」,而「目標列」座標,請注意其邏輯意義與我們通常使用的參數的區別。這裡dstRow相當於 destY, dstCol就相當於 destX 參數,即(dstCol, dstRow) 是在目標區域中的起始座標,這一點需要注意。
最後一個參數 platformContext 以 windows系統為 HDC 。
其中第一個參數我們仍需要再做簡單介紹,即 PSPixelMap 的定義和 PS 對此資料區的分佈要求。 PSPixelMap的定義如下:
typedef struct PSPixelMap
{
int32 version;
VRect bounds;
int32 imageMode;
int32 rowBytes;
int32 colBytes;
int32 planeBytes;
void *baseAddr;
//---------------------------------------------------------------------------
// Fields new in version 1:
//---------------------------------------------------------------------------
PSPixelMask *mat;
PSPixelMask *masks;
// Use to set the phase of the checkerboard:
int32 maskPhaseRow;
int32 maskPhaseCol;
//---------------------------------------------------------------------------
// Fields new in version 2:
//---------------------------------------------------------------------------
PSPixelOverlay *pixelOverlays;
unsigned32 colorManagementOptions;
} PSPixelMap; 登入後複製
#
◆ version: 結構體版本。 對於PS CS版本來說,它要求我們將它設為1。 PS的未來版本可能會擴展它並提升此版本號。
◆ ):像素資料所佔據的長方形。
◆ imageMode:資料區的影像模式。
它支援下列模式: grayscale, RGB, CMYK, Lab。
◆ rowBytes: 相鄰行間的位元組數距離。
相當於掃描程式寬度(重要),以位元組為單位,且必須設定正確。
◆ colBytes: 鄰近列像素資料的位元組數距離。
由於資料是「集中分佈」的,所以這個屬性的值主要取決於像素的色深度。對於每個通道每個像素使用一個位元組的普通24位元深度的圖像來說,這個距離為1byte。
◆ planeBytes: 鄰近通道的位元組數距離。
由於資料是「集中分佈」的,因此這個特性通常是 rowBytes * 影像高度。
◆ baseAddr:資料區起始位址。
我曾在先前的文章中曾被檢視,PS提供給我們的 inData 和 outData 是通道交叉分佈的(interleave)。而這裡PSPixelMap中的資料的分佈需求則不同,它要求資料是通道集中分佈的。
例如對RGB影像來說, 當我們要求所有頻道時,inData/outData中的資料分佈為:
G | B | ..... (interleave)
而而對PSPixelMap 中的資料來說,則要求依下列分佈:
G | G | ... B | B | B .... (集中分佈)
也就是說對inData 與outData,所有頻道資料交叉分佈,同一個頻道(plane)的資料在資料區中是跳躍式存在的。
而對 PSPixelMap 來說,同一個通道(plane)的資料是集中在一起的,先列出所有第一個通道數據,再列出所有第二個通道數據,等等。
同時我們進行像素定位與預測緩衝區大小時,與普通的Bitmap像素定位一樣,必須使用濾鏡參數中的inRowBytes 屬性(相當於掃描行寬度)進行在「行」間定位,而不能假設或自行計算“行寬度”。
【注意】我們必須清楚資料分佈的細節,這樣才能正確定位到指定位置的像素。
(2.2)控制縮放:FilterRecord中的inputRate 與inputPadding 屬性; #
1# 因此我們顯示縮圖時必然面對的一個問題是縮放問題。大多數情況下,由於縮圖只是定性展示,對資料精確性要求可以有所降低,並且考慮到性能因素,因此一般縮圖的尺寸可以設定的較小,當原圖(filterRect)比縮圖( bannerRect)大時,我們希望影像縮小顯示在縮圖上。而當原圖比縮圖小時,我們就採用實際原圖大小(即縮放因子=1)即可。因此現在我們需要了解如何把原圖縮小到縮圖大小。在GDI中我們知道我們可以使用
StretchBlt 函數來完成縮放的。而在這裡我們取得來源圖資料是從PS傳遞給我們的inData得到的,當我們希望得到縮小的原圖時,我們也就是透過設定 FilterRecord 參數中的 inputRate (取樣率)屬性來完成縮小。
(2.2.1)Fixed inputRate;
==== =================================
【關於Fixed 類型】
在PS中定義為long 類型:
typedef long Fixed;
所謂Fixed是相對 float(浮點小數)來說的。在float中小數點的位置是不固定的,因此稱為浮動點。而 Fixed 則把一個小數拆解為整數部分和小數部分,分別儲存到一個高16位元和低16位元。即其意義是 "16.16"。
例如假設一個小數是3.00f;則對應的Fixed數是 0x00030000;
㠀#
double _factor;
## #Fixed _fixed = ( Fixed ) ( _factor * 0x10000 ) ; #
##
從Fixed 類型轉換成浮點型別的方法是( 備註:1 / 0x10000 = 0.0000152587890625 ) :
# Fixed _fixed;
#
double
#
#fa_f
##
1_faf ## nx *
0.0000152587890625
;
======================= ===========================
inputRate表示取樣率,在邏輯意義上是一個小數,我們透過設定它的值來實現得到的inData是對原圖的縮放結果。在預設情況下,PS設定的inputRate就是1,也就沒有任何縮放。我們在取得縮圖資料時,需要計算縮放因子(factor),然後把inputRate設定為factor(注意資料類型的轉換方法)。
設定為這樣設定後,我們得到的inData 的實際影像座標將會是inRect * inputRate,也就是
inRect.right * inputRate, inRect.bot .0 時,則每兩個像素採樣一個點,則inRect, inData 的關係如下圖所示:
在此中針對需要把我們希望的inRect(粉紅矩形區域)的座標除以inputRate(圖中假設inputRate = 2),才是需要提交給PS的inRect(上圖的藍色矩形)。然後我們使用advanceProc回呼函數,即可得到 inData 為上圖中右側的影像數據,可見它的尺寸在兩個方向上都縮小了一半。
【注意1】處理完畢縮圖並關閉對話方塊時,必須將inputRate恢復為 0x00010000 (1.0)。否則它將繼續影響後續的實際處理中的 inData!使處理結果產生意料外的結果。 ###### 【注意2】濾鏡參數必須考慮影像縮放所帶來的影響。和縮放有關的參數也要對應的映射到縮圖尺寸上(例如本例中的隨機抖動距離要同比例縮小)。和縮放無關的參數(例如本例中的不透明度百分比,填充色)可不考慮縮放影響。 ###### ###### ###(2.2.2)int16 inputPadding;######### 可以指定補齊的像素的值(0~255),也可以設定為以下選項(它們被定義為負數,以和使用者設定像素值區別):###### ###
plugInWantsEdgeReplication : 复制边缘像素。
plugInDoesNotWantPadding :随机值(不确定的值)。
plugInWantsErrorOnBoundsException :(默认值)请求边界外数据时标记一个错误。
当请求区域超出边界,PS将使用上述选项设置 inData 的数据。
(2.2.3)显示缩略图。
为了显示缩略图,我们需要请求PS为我们分配缓冲区。我们首先需要预测我们的缓冲区的大小,并在Prepare调用时通知 PS 我们的需求。
考虑到当用户在对话框上进行参数调整时,我们应该实时的更新缩略图显示,以反馈当前参数效果。所以我们需要两份缩略图数据,一份是缩略图的原始数据,它作为算法的输入,在创建对话框时获取到源图数据,然后在整个对话框生命期间保持数据不会改变。另一份是我们用于处理 WM_PAINT 消息时使用的绘制数据,即可以实时改变的缩略图实际显示数据。
因此我们评估缩略图的尺寸,然后使用以下估计值:
bufferSize = 缩略图最大宽度 * 缩略图最大高度 * 通道数 * 2;
在 Prepare 调用期间,我们把这个值(bufferSize)设置到 FilterRecord 的 bufferSpace 和 maxSpace 属性中,这表示我们(PlugIn)和PS(Host)进行内存需求“协商”,使 PS 了解到我们预期的内存开销,然后尝试准备足够内存以供我们后续的申请。
真正显示对话框是在 start 调用中,我们在对话框的初始化消息时准备请PS为我们申请缓冲区。基本方式如下:
//获取 buffer 回调函数集指针
BufferProcs *bufferProcs = gFilterRecord->bufferProcs;
//请PS为我们申请内存
bufferProcs->allocateProc(bufferSize, &m_ProxyData.bufferId0);
//请PS为我们锁定内存(禁止内存整理)
//[ 1 ]函数返回被锁定的内存起始地址。
//[ 2 ]第二个参数是BOOL moveHigth,对windows平台将被忽略。
m_ProxyData.data0 = bufferProcs->lockProc(m_ProxyData.bufferId0, TRUE);
//=============================
// 这里是处理和更新缓冲区的期间
//=============================
//使用结束后,释放和解锁缓冲区。
//解锁
gFilterRecord->bufferProcs->unlockProc(m_ProxyData.bufferId0);
//释放内存
gFilterRecord->bufferProcs->freeProc(m_ProxyData.bufferId0); 登入後複製
我们使用 lockProc 锁定缓冲区这块内存,主要是防止操作系统在我们处理数据期间进行内存整理,从而破坏缓冲区资源。
【注意】这里加锁和解锁使用的是“引用计数”机制,即解锁次数 必须 匹配加锁次数才能使缓冲区真正得到解锁。
为了显示缩略图,并能够实时反馈用户的调节,我们准备了下面的四个函数(其中CreateProxyBuffer 和 UpdateProxy 难度最大):
● CreateProxyBuffer
计算缩略图实际大小和缩放因子,委托PS为我们申请缓冲区,同时也初始化了原始数据(即把inData拷贝到PsPixelMap中),在处理 WM_INITDIALOG 时调用。
● UpdateProxy
当用户在对话框上修改了某个参数时(WM_COMMAND)被调用,用于更新缩略图显示数据,并刷新缩略图显示。会引起对 PaintProxy 函数的间接调用。
● PaintProxy
绘制缩略图,通过 displayPixels 回调函数完成,在处理 WM_PAINT 消息时调用。
● DeleteProxyBuffer
释放我们申请的缓冲区,在对话框退出前(WM_DESTROY)调用。
现在总结一下上面四个函数的调用时机,使我们对这四个函数的分工具有一个明确的认识,如下表:
#視窗訊息
##事件
#被呼叫的函數
#說明
WM_INITDIALOG
建立對話框
CreateProxyBuffer
#申請縮圖緩衝區並初始化
#WM_COMMAND
##########修改參數值###################UpdateProxy##############################UpdateProxy################# #更新縮圖,將間接呼叫###PainProxy#####################WM_PAINT######################################################################################### ##視窗繪製######PaintProxy
#繪製縮圖
##WM_DESTROY
#退出對話方塊
##DeleteProxyBuffer
釋放縮圖緩衝區
#
【注意】把 inData 拷贝到 PSPixelMap, 是一个难度很大,并且特别需要注意的地方。两块数据的通道数据的分布不同,因此像素定位方式也完全不同。并且涉及到缓冲区大小的计算和申请。 复制缓冲区时是使用指针进行访问的,而这非常容易因为引发错误(将导致PS进程崩溃)。
在CreateProxyBuffer中,我们的主要任务是分配缓冲区,然后把源图数据(inData)相应的拷贝到我们的缓冲区(绘制时设置给PSPixelMap结构)。由于这是一个有难度的地方,因此我特别把这个函数代码放在此处展示,代码如下:
//定义描述缩略图数据的结构(在CommonDefine.h中定义)
typedef struct _PROXYDATA
{
int left;//缩略图左上角客户区坐标
int top;
int width;//缩略图实际尺寸(像素)
int height;
int rowbytes; //扫描行宽度(bytes)
int planebytes; //通道间的距离(bytes)
float factor; //原图和缩略图之间的缩放因子
Ptr data0; //缩放后的原始数据块(即inData的一份拷贝),通过设置inputRate。
Ptr data1; //缩放后的显示数据块(用于即时性更新缩略图)
BufferID bufferId0; //data0的bufferId
BufferID bufferId1; //data1的bufferId
} PROXYDATA;
//用于缩略图缓冲区数据的参数
PROXYDATA m_ProxyData;
//申请缩略图内存,并申请缩略图数据
void CreateProxyBuffer()
{
int filterWidth = gFilterRecord->filterRect.right - gFilterRecord->filterRect.left;
int filterHeight = gFilterRecord->filterRect.bottom - gFilterRecord->filterRect.top;
int bannerWidth = m_RectBanner.right - m_RectBanner.left;
int bannerHeight = m_RectBanner.bottom - m_RectBanner.top;
float f1 = (float)filterWidth / bannerWidth;
float f2 = (float)filterHeight / bannerWidth;
m_ProxyData.factor = max(f1, f2);
//如果原图比缩略图小
if(m_ProxyData.factor < 1.0f)
{
m_ProxyData.factor = 1.0f;
m_ProxyData.width = filterWidth;
m_ProxyData.height = filterHeight;
}
else
{
//原图比缩略图大,则计算缩略图的实际尺寸
//把factor去除小数部分!因为我们不知道怎么把小数部分转换到Fixed的LOWORD。
m_ProxyData.factor = (int)(m_ProxyData.factor + 1.0f);
m_ProxyData.width = (int)(filterWidth / m_ProxyData.factor);
m_ProxyData.height = (int)(filterHeight / m_ProxyData.factor);
}
//设置缩略图左上角坐标(居中显示)
m_ProxyData.left = m_RectBanner.left + (bannerWidth - m_ProxyData.width)/2;
m_ProxyData.top = m_RectBanner.top + (bannerHeight - m_ProxyData.height)/2;
//想PS请求原始数据,用于填充data0
gFilterRecord->inRect.left = (int)(gFilterRecord->filterRect.left / m_ProxyData.factor);
gFilterRecord->inRect.top = (int)(gFilterRecord->filterRect.top / m_ProxyData.factor);
gFilterRecord->inRect.right = (int)(gFilterRecord->filterRect.right / m_ProxyData.factor);
gFilterRecord->inRect.bottom = (int)(gFilterRecord->filterRect.bottom / m_ProxyData.factor);
//通知 P S我们希望的补充数据(未知区域的填充数据)
gFilterRecord->inputPadding = 255; //plugInWantsEdgeReplication;
//通知 PS 输入采样率
//PS中,Fixed数字是用DWORD表示小数,HIWORDF表示整数部分,LOWORD表示小数部分。即 "ffff.ffff"
WORD hiword = (WORD)(m_ProxyData.factor);
gFilterRecord->inputRate = (hiword << 16);
//现在我们请求第一个通道的数据,以从PS那里获取一些必须的信息
gFilterRecord->inLoPlane = 0;
gFilterRecord->inHiPlane = 0;
//请求PS为我们更新InData
gFilterRecord->advanceState();
//现在我们委托PS申请缓存空间,为了简单,我们假设内存充裕,不会失败
int inHeight = gFilterRecord->inRect.bottom - gFilterRecord->inRect.top;
//扫描行宽度 * inRect高度 * 通道数
int bufferSize = gFilterRecord->inRowBytes * inHeight * gFilterRecord->planes;
//获取 buffer 回调函数集指针
BufferProcs *bufferProcs = gFilterRecord->bufferProcs;
//请PS为我们申请内存
bufferProcs->allocateProc(bufferSize, &m_ProxyData.bufferId0);
bufferProcs->allocateProc(bufferSize, &m_ProxyData.bufferId1);
//请PS为我们锁定内存(禁止内存整理)
//[ 1 ]函数返回被锁定的内存起始地址。
//[ 2 ]第二个参数是BOOL moveHigth,对windows平台将被忽略。
m_ProxyData.data0 = bufferProcs->lockProc(m_ProxyData.bufferId0, TRUE);
m_ProxyData.data1 = bufferProcs->lockProc(m_ProxyData.bufferId1, TRUE);
//注意提供给displayPixels函数的数据不是interleave分布,而是一个通道0,通道1,通道2(集中分布)!
//也就是 R R R R | G G G G | B B B B |
//现在我们把得到的通道interleave分布的数据转换为通道集中分布
uint8* p0=(uint8*)m_ProxyData.data0;
//我们复制第一个通道的数据到data0起始处
m_ProxyData.planebytes = gFilterRecord->inRowBytes * inHeight;
memcpy(p0,(void*)gFilterRecord->inData, m_ProxyData.planebytes);
//复制其他通道
for(int i=1; i<gFilterRecord->planes; i++)
{
gFilterRecord->inLoPlane = i;
gFilterRecord->inHiPlane = i;
//请求PS为我们更新InData
gFilterRecord->advanceState();
memcpy(p0 + i * m_ProxyData.planebytes,(void*)gFilterRecord->inData, m_ProxyData.planebytes);
}
//设置扫描行宽度
m_ProxyData.rowbytes = gFilterRecord->inRowBytes;
} 登入後複製
在上面的函数(CreateProxyBuffer)中,我们首先按照下面的方法计算出缩略图的缩放因子:
factor = ceiling (max(原图宽度 / 缩略图宽度, 原图高度 / 缩略图高度));
然后我们计算了缩略图的起始点坐标(m_ProxyData.left, m_ProxyData.top)和采用上述缩放因子后的缩略图实际尺寸(m_ProxyData.width, m_ProxyData.height)。请注意,我们把 factor 向上取整(ceiling),这会使缩略图的实际尺寸是小于等于其 BANNER 尺寸的。通过设置左上角坐标,我们使缩略图的位置在 BANNER 矩形中居中。
然后我们委托 PS 为我们分配两块同样大小的缓冲区 data0 和 data1(一个原图数据拷贝,一个是用于即时显示)并锁定它们。我们使用了PS提供的 advanceState 回调去请求原图数据,我在此前的文章中已经介绍过这个最重要的回调函数之一,它的作用是请求 Photoshop 立即更新滤镜参数(FilterRecord)结构中的相关数据,包括inData,outData等等。请注意在上面的代码中,我们是逐个通道进行复制的,即我们每次请求PS为我们发送一个通道的数据,然后我们把这批数据一次性的完全拷贝到缓冲区(使用memcpy),这样就完成了通道数据的“集中分布”。其中每个通道字节数(planeBytes)计算方法如下:
每个通道字节数(planeBytes) = 单一通道的扫描行宽度(inRowBytes) * 缩略图的图像高度(inRect高度);
我们把缩略图数据的信息并保存在m_ProxyData参数中。在 PaintProxy 中,我们只需要把这些信息再设置并提交给 displayProxy 回调函数即可。显示缩略图(PaintProxy,UpdateProxy)的主要逻辑和代码原理,限于篇幅这里不详细讲述,可参考附件中的源代码。最后我们可以看下滤镜的对话框运行效果如下:
当在上面的滤镜对话框中使用鼠标拖动或者键盘改变文本框数值时,左侧缩略图将会实时更新以反应当前的参数效果。在参数设置对话框中,我模拟了一个Photoshop中常见的UI特性,当你把鼠标悬停在数值文本框的左侧标签上时,光标变为一个拖动箭头的形状,这时按下鼠标,左右拖动,可以看到相应文本框的数据发生变化(这和操作滑杆控件非常类似)。在上面这个对话框中,你能够看到我如何模拟了PS的这种UI效果(在Photoshop看似朴素的外表下,隐藏着非常多让人惊叹的 UI 效果,而这只是它们中的其中一个,向强大的Photoshop致敬!)。
(3)增加一个我们自己定义的“关于对话框”。
在先前為了簡單起見,並在「關於」中只彈出了一個MessageBox。我們可以自訂一個關於對話框,同樣這裡我吸取了PS 的關於對話框的建議和風格,即沒有標題欄,沒有任何按鈕,對話框初始位置在其父窗口的中等偏上(上1/3 )處。使用者按Escape,回車鍵 或用滑鼠點選任何位置即退出對話方塊。我的濾鏡的關於對話框如下(在PS中點擊菜單:幫助-> 關於增效工具-> FillRed Filter... ):
這是一個普通的對話框,但我主要想介紹是當滑鼠移到我的部落格的網址上時,遊標變成(IDC_HAND)手形,點選即可使用預設瀏覽器開啟網址。它是用過使用PS的回調函數集中的對應函數來完成的。因此這裡我將示範 PS callback suites 的一種標準用法:
char url[256];
//函数集指针
PSGetFileListSuite4 *suite;
//获取GetFileList 回调函数集(callback suite)的指针
SPErr err = sSPBasic->AcquireSuite(
kPSGetFileListSuite, //suite name
kPSGetFileListSuiteVersion4, //suite version
(const void**)&suite //suite pointer
);
if(err) return TRUE;
//获取网址
GetDlgItemText(hDlg, IDC_STATIC_MYBLOG, url, sizeof(url));
//用默认浏览器打开网址
suite->BrowseUrl(url);
//释放suite
sSPBasic->ReleaseSuite(kPSGetFileListSuite, kPSGetFileListSuiteVersion4); 登入後複製
char url[ 256 ]; // 函数集指针 PSGetFileListSuite4 * suite; // 获取GetFileList 回调函数集(callback suite)的指针 SPErr err = sSPBasic -> AcquireSuite( kPSGetFileListSuite, // suite name kPSGetFileListSuiteVersion4, // suite version (const void ** ) & suite // suite pointer );if (err) return TRUE; // 获取网址 GetDlgItemText(hDlg, IDC_STATIC_MYBLOG, url, sizeof (url)); // 用默认浏览器打开网址 suite -> BrowseUrl(url); // 释放suite sSPBasic -> ReleaseSuite(kPSGetFileListSuite, kPSGetFileListSuiteVersion4);
在上面的代码中我们可以看到, PS CALLBACK Suites的用法 和 COM 组件的 QueryInterface 的使用方法是完全类似的:先声明想获取的回调函数集(callback Suite,一个含有一组PS内部的函数指针的struct)的一个指针,然后把该指针的地址传递给 BasicSuite 的 AcquireSuite 函数,成功以后我们就可以通过该指针去调用PS提供给插件的相应回调函数。
(4)总结。
到目前为止,我们已经完整的讲解了有关制作一个Photoshop滤镜的主要技术环节,从(1)创建项目,到(2)添加UI资源,再到(3)使Photoshop Scripting System知道我们的滤镜,并支持“动作”面板的对话框选项,以及本篇重点讲述的添加在对话框上的缩略图。涵盖了制作 Photoshop 滤镜插件的流程和重要知识,而Photoshop插件开发的技术细节以及插件种类仍然是非常繁复众多的,有待进一步的研究。
我們開發Photoshop外掛程式的一個主要原因是,PS是圖形處理領域的重要軟體,為第三方開放了插件擴充的介面。身為第三方開發者我們可以依照自己的需求,遵照PS的約定去以插件形式擴展PS。在PS的重要使用者基礎上,擴展和研究將會更有實際意義。
製作濾鏡的基本技術已被介紹完成,而剩餘的其他工作則主要是針對影像處理演算法的尋求與發掘。
本例是使用基於Platform SDK的Windows程式開發為基礎的,但重點在於解說PS外掛程式開發,因此沒有詳細解說Windows程式開發中的一些技術細節。
更多怎麼寫一個Photoshop濾鏡 -- 在對話框上增加縮圖 相關文章請關注PHP中文網!
#
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
作者最新文章
1970-01-01 08:00:00
1970-01-01 08:00:00
1970-01-01 08:00:00
1970-01-01 08:00:00
1970-01-01 08:00:00
1970-01-01 08:00:00
1970-01-01 08:00:00
1970-01-01 08:00:00
1970-01-01 08:00:00
1970-01-01 08:00:00