前の記事では、PS の「アクション」パネルのサポートに適した PS のスクリプト システムでフィルタを認識および記述できるように、フィルタに用語リソースを追加する方法について説明しました。この記事では、パラメーター ダイアログ ボックスにリアルタイム プレビュー用の小さなサムネイルを追加するなど、前のデモをさらに改良します。ダイアログ ボックスの導入は主に、フィルターで使用される画像処理アルゴリズムを設定または調整する機会とインターフェイスをユーザーに提供することを目的としています。通常、UI の使いやすさの尺度として、ダイアログ ボックスにプレビューを表示して、結果に対するパラメーターの影響を直感的にユーザーにフィードバックし、パラメーターの調整をガイドできるようにする必要があります。ユーザーがフィルタ コマンドを繰り返し実行して効果を確認し、パラメータを調整する必要はありません。添 以前、「タップ画像を追加する」という機能は難しくないと思っていたのですが、実際にやってみると、以前書いた記事の説明よりもはるかに難しいことがすぐにわかりました。 PS が提供するコールバック関数を使用してサムネイルを表示しようとする場合、スケーリング、データ分散、走査線、その他の詳細に影響を与えるパラメーター設定を含む、PS が提供するインターフェイスの詳細を完全に理解しておく必要があるためです。間違いがあってはなりません。そうでないと、異常な表示が表示されたり、誤ってメモリの限界を超えてしまったりする可能性があります。
サムネイルを導入する前に、まずフィルター アルゴリズムにいくつかの興味深い改善を加え、より実用的なものにするために少し機能強化を加えました。
(1) 「ピクセルランダムジッター」パラメータとアルゴリズムを導入します。
前にピクセルを設定したとき、入力と出力の位置はまったく同じでした。つまり、Dest(i, j) = f (Src(i, j)) でした。ここで、上記の式に少し変更を加え、ソース ピクセルをランダムにディザリングすること、つまり Dest(i, j) = f (Src(i+dx, j+dy)) を検討します。
ジッター距離を距離 (ピクセル) パラメーターに設定します。これにより、ソース ピクセルを取得するときに、現在のピクセルを中心として周囲に距離を延長した正方形内の点がソース ピクセルとしてランダムに選択されます。 。これにより、結果のグラフに「溶解」または「侵食」効果が与えられます。以下の図に示すように:はフィルター パラメーターの距離パラメーターを増加させ、上の図のランダム ジッター距離を示しています。このようにして、位置 (i, j) にピクセルを設定するとき、取得するソース ピクセル座標は次のとおりです:
()%(2*距離+1) - 距離;
実際の処理では、次のことも必要です。上記の結果では、x と y が有効なデータ境界を超える可能性があるため、x と y を filterRect に制限する必要があることを考慮してください。 ○ タイル加工法を採用しているため、調整には多少の熟練が必要です。 PS から要求する inRect を変更します。つまり、パッチを適用するたびに、outRect は以前と同じままになり、距離ピクセルによって inRect をすべての辺に拡張しようとします。これにより、毎回有効なデータを取得できるようになります。パッチが filterRect の端にない限り、パッチを適用するとき (パッチが filterRect の内側にあるとき)。 Inrect は OutRCT よりも「大きな円」であるため、この時点では、2 つの RECT のピクセルは同じサイズで同じ重なり合いではなく、一定のオフセットがあることに注意する必要があります。コードでは、2 つの長方形間のオフセット関係を考慮する必要があります。私のソースコードはこちらを参照していただくとして、処理方法の詳細な説明は省略します。 「木mbnail" (プロキシ) をダイアログ ボックスに追加します。パラメーターを追加し、収縮ダイアグラムを表示するためにダイアログ ボックスの左側の小さな領域を空けました。これにより、ズーム ダイアグラムに非表示の静的コントロール (プロキシ バナー) を配置しました。実行時の「サムネイル」の(クライアント領域の座標)。変更されたダイアログ ボックスは次のとおりです。
サムネイルを表示するときは、この関数のプロトタイプである gFilterRecord displayPixels コールバック関数を使用します。は次のとおりです (関数ポインタの typedef): typedef MACPASCAL OSErr (*DisplayPixelsProc) const PSPixelMap *source, const vrect *srcrect、int32 PlatformContext);最初のパラメーターは、Pixelデータ領域を記述するPSPIXELMAP構造のポインターです。 「ターゲット列」はターゲット領域の座標です。通常使用するパラメータとは論理的な意味が異なることに注意してください。ここで、dstRow は destY に相当し、dstCol は destX パラメータに相当します。つまり、(dstCol, dstRow) は対象領域の開始座標です。
最後のパラメータ platformContext は、Windows システムの HDC です。 cirst最初のパラメーター、つまり、このデータ領域の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: データ領域の画像モード。
次のモードをサポートしています: グレースケール、RGB、CMYK、Lab。
◆ rowBytes: 隣接する行間のバイト単位の距離。
走査線幅 (重要) に相当する値 (バイト単位) を正しく設定する必要があります。 ◆ ColBytes: ピクセル データの隣接する列間のバイト単位の距離。
データは「集中的に分散」されているため、この属性の値は主にピクセルの色深度に依存します。チャネルごとにピクセルごとに 1 バイトを使用する通常の 24 ビット深度イメージの場合、この距離は 1 バイトです。
◆ planBytes: 隣接するチャネル間のバイト単位の距離。
データは「集中分散」されているため、通常、この属性は rowBytes * 画像の高さになります。
◆baseAddr: データ領域の開始アドレス。
PS から提供される inData と outData がチャネル間分散 (インターリーブ) されていることを以前の記事で述べました。ここでの PSPixelMap のデータの配布要件は異なります。データは一元的に配布される必要があります。たとえば、RGB 画像の場合、Indata/OUTDATA のデータ分布は次のようになります。 ️つまり、Indata と Outdata では、すべてのチャネルのデータがクロス分散され、同じチャネル (Plane) のデータがデータ領域に存在します。 X PSPixelmap の場合、同じチャネル (Plane) のデータが集中しているため、最初に 1 番目のチャネルのデータをすべてリストし、次に 2 番目のチャネルのデータをすべてリストする、というようになります。同時に、ピクセルの位置決めと予測バッファーを実行します。通常のビットマップのピクセルの位置決めと同様に、フィルター パラメーターの InRowbytes プロパティ (スキャン幅に相当) を使用して、「スキャン幅」の間の位置を特定する必要があります。想定できない、または想定できない「行幅」を自分で計算します。
[注意]指定された位置のピクセルを正確に特定できるように、データ分布の詳細を知る必要があります。
(2.2) スケーリングの制御: FilterRecord の inputRate プロパティと inputPadding プロパティ。 FilterRecord の inputRate プロパティと inputPadding プロパティ。ほとんどの場合、サムネイルは定性的な表示にすぎないため、データの精度に対する要件は緩和され、元の画像 (filterRect) がサイズよりも大きい場合は、パフォーマンス要因を考慮してサムネイルのサイズを小さく設定できます。サムネイル(bannerRect)が大きいので、画像を縮小してサムネイル上に表示したいと思います。元の画像がサムネイルより小さい場合は、実際の元の画像サイズ (つまり、倍率 = 1) をそのまま使用します。そこで、元の画像をサムネイル サイズに縮小する方法を理解する必要があります。 GDI では、
StretchBlt
関数を使用してスケーリングを完了できることがわかっています。ここでは、PS から渡された inData からソース画像データを取得します。縮小された元の画像を取得したい場合は、FilterRecord パラメーターに inputRate (サンプリング レート) 属性を設定することで縮小を完了します。 ================実際の意味は、(固定小数点)小数点です。いわゆるFixedはfloat(浮動小数点10進数)に相対的です。 float では小数点の位置が固定されていないため、浮動小数点と呼ばれます。 「Fixed」は、10 進数を整数部と小数部に分解し、それぞれ上位 16 ビットと下位 16 ビットに格納します。つまり、「16.16」という意味になります。> * 0x10000 ) ;
固定型から浮動小数点型への変換方法は(備考: 1 / 0x10000 = 0.0000152587890625 ):
固定_固定
double
_factor = _fixed * 0.0000152587890625 ;
= ============= ============= =======================
サンプリング レートを示す INPUTRETE は、論理的な意味では 10 進数であり、その値を inData が元の画像のスケーリング結果になるように設定することで実現できます。デフォルトでは、PS によって設定される inputRate は 1 であり、スケーリングはありません。サムネイル データを取得するときは、スケーリング係数 (factor) を計算し、inputRate を係数に設定する必要があります (データ型の変換方法に注意してください)。 inRect.right * inputRate, inRect.bottom * INPUTRATE) たとえば、inputrate が 2.0 の場合、infancy と indata の関係は次の図に示されます。
は、ズーム内のズームを示しています。inRect はどのように設定すればよいですか? 必要な長方形を取得するには、必要な inRect (ピンクの長方形領域) の座標を除算する必要があることに注意してください。 inputRate (図では inputRate = 2 と仮定)。これは PS に送信する必要がある inRect (上の画像の青い四角形)。次に、advancedProc コールバック関数を使用して、上図の右側の画像データとして inData を取得します。そのサイズが両方向で半分に削減されていることがわかります。理 [注1] タップされたダイアグラムを処理してダイアログボックスをオフにする場合、INPUTRATE を 0x00010000 (1.0) に戻す必要があります。そうしないと、後続の実際の処理で inData に影響を及ぼし続けることになります。処理によって予期しない結果が生じる原因となります。镜 [注 2] フィルタ パラメータは画像のスケーリングの影響を考慮する必要があります。スケーリングに関連するパラメータも、それに応じてサムネイル サイズにマッピングする必要があります (たとえば、この例のランダム ジッター距離は比例的に減少する必要があります)。スケーリングに関係のないパラメータ (この例では不透明度のパーセンテージや塗りつぶしの色など) は無視できます。 2
(2.2.2) int16 inputpadding;PS が Indata を提供する場合、それを補完できます。塗りつぶされたピクセルの値 (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
| サムネイルを更新します。これにより、PainProxy | が間接的に呼び出されます。
| 窓の絵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)增加一个我们自己定义的“关于对话框”。
簡単にするために、「About」にメッセージボックスをポップアップしました。ダイアログ ボックスをカスタマイズできます。ここでは、PS の提案とダイアログ ボックスのスタイルも採用しています。つまり、タイトル バーもボタンもなく、ダイアログ ボックスの初期位置は親ウィンドウ (上1/3)の位置にあります。ユーザーは Escape キー、Enter キーを押すか、マウスの任意の場所をクリックしてダイアログ ボックスを終了します。私のフィルタの [バージョン情報] ダイアログ ボックスは次のとおりです (PS のメニューをクリック: [ヘルプ] -> [プラグインについて] -> [FillRed フィルタ...] をクリックします): 主に紹介したいのは、マウスが移動したときです。私のブログの URL を入力すると、カーソルが手の形 (IDC_HAND) に変わり、クリックするとデフォルトのブラウザを使用して URL を開くことができます。これは、PS のコールバック関数セット内の対応する関数を使用して行われます。そこで、ここでは PS コールバック スイートの標準的な使用法を示します: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 プラグインを開発する主な理由の 1 つは、PS がグラフィック処理の分野で重要なソフトウェアであり、サードパーティにプラグイン拡張インターフェイスを提供するためです。サードパーティ開発者として、私たちは独自のニーズに応じて、また PS の規約に従って、プラグインの形で PS を拡張できます。 PS の重要なユーザーベースに基づいて、拡張と研究はより現実的になります。本 フィルタ作成の基本技術は紹介しましたが、残りの作業は主に画像処理アルゴリズムの探索と発掘になります。より この例は、Platform SDK に基づいた Windows プログラムの開発に基づいていますが、PS プラグインの開発の説明に重点を置いているため、Windows プログラムの開発における一部の技術的な詳細については詳細な説明はありません。
Photoshop フィルターの作成方法 - ダイアログ ボックスにサムネイルを追加する方法の詳細については、PHP 中国語 Web サイトの関連記事に注目してください。