Maison > interface Web > Tutoriel PS > Comment écrire un filtre Photoshop - ajouter une vignette à la boîte de dialogue

Comment écrire un filtre Photoshop - ajouter une vignette à la boîte de dialogue

高洛峰
Libérer: 2017-02-23 09:16:56
original
2050 Les gens l'ont consulté

Dans l'article précédent, nous avons expliqué comment ajouter des ressources terminologiques aux filtres, afin que nos filtres puissent être perçus et décrits par le système de script de PS, donc un support convivial pour le panneau "Action" de PS. Dans cet article, nous affinerons davantage la DEMO précédente, par exemple en ajoutant une petite vignette pour un aperçu en temps réel dans la boîte de dialogue des paramètres. L'introduction de la boîte de dialogue vise principalement à donner aux utilisateurs la possibilité et l'interface de définir ou d'ajuster l'algorithme de traitement d'image utilisé par le filtre. Habituellement, pour mesurer la convivialité de l'interface utilisateur, un aperçu doit être fourni sur la boîte de dialogue, afin que l'impact des paramètres sur les résultats puisse être intuitivement renvoyé à l'utilisateur et le guider dans l'ajustement des paramètres. Au lieu de demander aux utilisateurs d'exécuter à plusieurs reprises des commandes de filtre pour voir l'effet, puis d'ajuster les paramètres.

Auparavant, je pensais que la fonction « ajouter une vignette » ne devrait pas être très difficile, mais lorsque j'ai essayé de le faire, j'ai vite découvert que c'était bien plus difficile que ce qui était expliqué dans les articles que j'avais écrits auparavant. Parce que lorsque nous essayons d'utiliser la fonction de rappel fournie par PS pour afficher des vignettes, nous devons être parfaitement clairs sur les détails de l'interface fournie par PS, y compris les paramètres qui affectent la mise à l'échelle, la distribution des données, les lignes de balayage et d'autres détails. Il ne peut y avoir aucune erreur, sinon nous pourrions voir un affichage anormal, ou même accidentellement faire sortir la mémoire des limites.

Avant d'introduire les vignettes, j'ai d'abord apporté quelques améliorations intéressantes à l'algorithme de filtrage et apporté une petite amélioration pour le rendre plus pratique.

                                                                                                                                                                   . Lorsque nous avons défini les pixels auparavant, les positions d'entrée et de sortie étaient complètement cohérentes, c'est-à-dire Dest(i, j) = f (Src(je, j)).

Nous envisageons maintenant d'apporter une petite modification à la formule ci-dessus et de tramer aléatoirement les pixels source, c'est-à-dire Dest(i, j) = f (Src(i dx, j dy)). Nous définissons la distance de gigue sur le paramètre distance (pixel), de sorte que lorsque nous prenons le pixel source, nous sélectionnons au hasard un point comme pixel source dans le carré avec le pixel actuel comme centre et en étendant le distance à la périphérie. Cela leur donne un effet de « dissolution » ou d'« érosion » dans le graphique résultant. Comme le montre la figure ci-dessous :

                                                                                                                                                                                                                                                             De cette façon, lorsque nous plaçons le pixel à la position (i, j), les coordonnées du pixel source que nous prenons sont :

    > y = j rand()%(2*distance 1) - distance;

Dans le traitement réel, nous devons également prendre en compte le résultat ci-dessus x, y peut dépasser la limite de données valide, donc x, y doivent être restreints à l'intérieur de filterRect. 怎样编写一个Photoshop滤镜 -- 在对话框上增加缩略图

Puisque nous utilisons une méthode de traitement des Carreaux, nos ajustements nécessitent un peu d'habileté. Nous modifierons l'inRect que nous demandons à PS, c'est-à-dire que chaque fois que nous patcherons, outRect restera le même qu'avant, et nous essaierons d'étendre inRect en pixels de distance de tous les côtés. Cela garantira que nous pouvons obtenir des données valides à chaque fois. au moment où nous corrigeons (lorsque le patch est à l'intérieur du filterRect), à moins que le patch ne soit sur le bord du filterRect.

Il faut noter que comme inRect est "plus grand" que outRect, les pixels des deux Rects n'ont plus la même taille et se chevauchent complètement, mais ont un certain biais

Move <.>

! Dans le code, nous devons considérer la relation de décalage entre les deux rectangles. Vous pouvez vous référer à mon code source ici, je n'expliquerai donc pas la méthode de traitement en détail.

                                                                                                                                                                                                              

Nous avons ajouté un paramètre, puis laissé une petite zone sur le côté gauche de la boîte de dialogue pour afficher les vignettes. Pour plus de commodité, j'ai placé un contrôle STATIQUE caché (bannière proxy) dans la position des vignettes. Sa fonction principale est. pour me permettre d'obtenir les limites (coordonnées de la zone client) de la "vignette" au moment de l'exécution. La boîte de dialogue modifiée est la suivante :

怎样编写一个Photoshop滤镜 -- 在对话框上增加缩略图

Lors de l'affichage des vignettes, nous utilisons la fonction de rappel displayPixels dans gFilterRecord. Le prototype de cette fonction est le suivant. suit (typedef du pointeur de fonction) : typedef MACPASCAL OSErr (*DisplayPixelsProc) ( const PSPixelMap *source, const VRect *srcRect , int32 dstRow,

int32 dstCol,

void * platformContext);

Le premier paramètre est un pointeur vers une structure PSPixelMap pour décrire une zone de données de pixels, qui est équivalente à l'image source dans BitBlt. Le paramètre srcRect décrit l'image source Rectangle ; Ici, dstRow est équivalent à destY et dstCol est équivalent au paramètre destX, c'est-à-dire que (dstCol, dstRow) est la coordonnée de départ dans la zone cible.

Le dernier paramètre platformContext est HDC dans le système Windows.

Nous devons donner une brève introduction au premier paramètre, c'est-à-dire la définition de PSPixelMap et les exigences de distribution de PS pour cette zone de données. La définition de PSPixelMap est la suivante :

   

◆ version : Version de structure.

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;
Copier après la connexion

Pour la version PS CS, cela nous oblige à le mettre à 1. Les futures versions de PS pourraient l'étendre et augmenter ce numéro de version.

     ◆ limites : le rectangle occupé par les données de pixels. ◆ imageMode : Mode image de la zone de données.

Il prend en charge les modes suivants : niveaux de gris, RVB, CMJN, Lab.      ◆ rowBytes : La distance en octets entre les lignes adjacentes.

L'équivalent de la largeur de la ligne de numérisation (important), en octets, doit être réglé correctement.

     ◆ colBytes : La distance en octets des colonnes adjacentes de données de pixels.

Les données étant "concentrées", la valeur de cet attribut dépend principalement de la profondeur de couleur du pixel. Pour une image de profondeur normale de 24 bits utilisant un octet par pixel et par canal, cette distance est de 1 octet.

    ◆ planeBytes : La distance en octets entre les canaux adjacents.

Étant donné que les données sont "distribuées de manière centralisée", cet attribut est généralement rowBytes * hauteur de l'image.

    ◆ baseAddr : adresse de départ de la zone de données.

J'ai mentionné dans des articles précédents que les inData et outData qui nous sont fournis par PS sont distribués sur plusieurs canaux (entrelacé). Les exigences de distribution des données dans PSPixelMap sont différentes ici. Elles nécessitent que les données soient distribuées de manière centralisée dans les canaux.

Par exemple, pour les images RVB, lorsque nous demandons tous les canaux, la distribution des données dans inData/outData est :

R G | B | ..... (entrelacé)

​​​​​​​​​​​​​​​Pour les données dans PSPixelMap, elles doivent être distribuées comme suit : G | | ... B | B | B .... (Distribution concentrée)

C'est-à-dire que pour inData et outData, toutes les données du canal sont distribuées de manière croisée, et les données du même canal (plan ) est dans les données. La zone existe à pas de géant.

Pour PSPixelMap, les données du même canal (plan) sont concentrées ensemble. Toutes les données du premier canal sont répertoriées en premier, puis toutes les données du deuxième canal sont répertoriées, et ainsi de suite.

En même temps, lorsque nous effectuons le positionnement des pixels et la taille du tampon de prédiction, tout comme le positionnement ordinaire des pixels Bitmap, nous devons utiliser l'attribut inRowBytes (équivalent à la largeur de la ligne de balayage) dans les paramètres de filtre pour positionner entre " rows". Vous ne pouvez pas assumer ou calculer vous-même la "largeur des lignes".

[Remarque] Nous devons connaître les détails de la distribution des données afin de pouvoir localiser correctement le pixel à la position spécifiée.

                                                                                                                        Par conséquent, l'un des problèmes auxquels nous devons faire face lors de l'affichage des vignettes est le problème de mise à l'échelle. Dans la plupart des cas, étant donné que les vignettes ne sont qu'un affichage qualitatif, les exigences en matière de précision des données peuvent être réduites et, en tenant compte des facteurs de performances, la taille des vignettes peut généralement être plus petite lorsque l'image d'origine (filterRect) est plus grande que la taille. la vignette (bannerRect) est grande, nous voulons que l'image soit réduite et affichée sur la vignette. Lorsque l'image originale est plus petite que la vignette, nous utilisons simplement la taille réelle de l'image originale (c'est-à-dire un facteur d'échelle = 1). Nous devons maintenant comprendre comment réduire l’image originale à la taille d’une vignette. Dans GDI, nous savons que nous pouvons utiliser la fonction

StretchBlt

pour terminer la mise à l'échelle. Ici, nous obtenons les données de l'image source à partir du inData qui nous a été transmis par PS. Lorsque nous voulons obtenir l'image originale réduite, nous complétons la réduction en définissant l'attribut inputRate (taux d'échantillonnage) dans le paramètre FilterRecord.

                                                                                                                     ================================ Il est défini comme un type long dans PS :

typedef longFixed;

Mais la signification réelle deFixed dans PS est une décimale (à virgule fixe). Le soi-disant fixe est relatif au flottant (décimal à virgule flottante). La position de la virgule décimale dans float n'est pas fixe, on l'appelle donc virgule flottante. Fixed désassemble une décimale en une partie entière et une partie décimale, et les stocke respectivement dans un 16 bits haut et un 16 bits bas. C'est-à-dire que sa signification est « 16,16 ». Par exemple, supposons qu'il existe un nombre décimal de 3,00f ; le nombre fixe correspondant est 0x00030000

La méthode de conversion d'un nombre à virgule flottante en type fixe est :

                                                                      🎜 >Fixed _fixed = ( Fixed ) ( _factor * 0x10000 )

;

 La méthode pour convertir à partir de Le type fixe en type à virgule flottante est (Remarques : 1 / 0x10000 = 0.0000152587890625) :

 Fixé _fixed;

double _factor = _fixed *

0.0000152587890625

; ============ ===============

inputRate représente le taux d'échantillonnage, qui est une décimale logique, nous définissons sa valeur pour se rendre compte que l'inData obtenu est le résultat de la mise à l’échelle de l’image originale. Par défaut, le inputRate défini par PS est 1 et il n'y a pas de mise à l'échelle. Lorsque nous obtenons les données miniatures, nous devons calculer le facteur de mise à l'échelle (factor), puis définir inputRate sur factor (notez la méthode de conversion du type de données). Après l'avoir défini ainsi, les coordonnées réelles de l'image de inData que nous obtiendrons seront inRect * inputRate, c'est-à-dire

Par exemple, lorsque inputRate est 2.0, un point est échantillonné tous les deux pixels, puis inRect. La relation entre inData est illustrée dans la figure ci-dessous : Nous devons diviser les coordonnées de l'inRect (zone rectangulaire rose) que nous voulons par inputRate (en supposant que inputRate = 2 dans le image) pour obtenir l'inRect (rectangle bleu dans l'image ci-dessus) qui doit être soumis à PS. Ensuite, nous utilisons la fonction de rappel advanceProc pour obtenir inData comme données d'image sur le côté droit dans la figure ci-dessus. On peut voir que sa taille a été réduite de moitié dans les deux sens.

【Remarque 1】Lorsque la vignette est traitée et que la boîte de dialogue est fermée, le inputRate doit être restauré à 0x00010000 (1.0). Sinon, cela continuera à affecter inData lors du traitement ultérieur ! Faire en sorte que le traitement produise des résultats inattendus.

【Remarque 2】Les paramètres du filtre doivent prendre en compte l'impact de la mise à l'échelle de l'image. Les paramètres liés à la mise à l'échelle doivent également être mappés en conséquence à la taille de la vignette (par exemple, la distance de gigue aléatoire dans cet exemple doit être réduite proportionnellement). Les paramètres qui n'ont rien à voir avec la mise à l'échelle (tels que le pourcentage d'opacité et la couleur de remplissage dans cet exemple) peuvent être ignorés.

 

(2.2.2) int16 inputPadding;

Lorsque PS fournit inData, il peut être complété. Vous pouvez spécifier la valeur des pixels remplis (0 ~ 255) ou la définir sur les options suivantes (ils sont définis comme des nombres négatifs pour les distinguer des valeurs de pixels définies par l'utilisateur) :

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);
Copier après la connexion

我们使用 lockProc 锁定缓冲区这块内存,主要是防止操作系统在我们处理数据期间进行内存整理,从而破坏缓冲区资源。

【注意】这里加锁和解锁使用的是“引用计数”机制,即解锁次数 必须 匹配加锁次数才能使缓冲区真正得到解锁。

为了显示缩略图,并能够实时反馈用户的调节,我们准备了下面的四个函数(其中CreateProxyBuffer 和 UpdateProxy 难度最大):

● CreateProxyBuffer

计算缩略图实际大小和缩放因子,委托PS为我们申请缓冲区,同时也初始化了原始数据(即把inData拷贝到PsPixelMap中),在处理 WM_INITDIALOG 时调用。

● UpdateProxy

当用户在对话框上修改了某个参数时(WM_COMMAND)被调用,用于更新缩略图显示数据,并刷新缩略图显示。会引起对 PaintProxy 函数的间接调用。

● PaintProxy

绘制缩略图,通过 displayPixels 回调函数完成,在处理 WM_PAINT 消息时调用。

● DeleteProxyBuffer

释放我们申请的缓冲区,在对话框退出前(WM_DESTROY)调用。

现在总结一下上面四个函数的调用时机,使我们对这四个函数的分工具有一个明确的认识,如下表:

Message de la fenêtre

Événement

Fonction appelée

Description

WM_INITDIALOG

Créer une boîte de dialogue

Créer un tampon proxy

Demander un tampon de vignettes et initialiser

WM_COMMAND

Modifier la valeur du paramètre

Mettre à jour le proxy

Mettre à jour la vignette, appellera indirectement PainProxy

WM_PAINT

Dessin de fenêtre

PaintProxy

Vignette de peinture

WM_DESTROY

Boîte de dialogue Quitter

SupprimerProxyBuffer

Libérer le tampon de vignettes

【注意】把 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;
}
Copier après la connexion

在上面的函数(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滤镜 -- 在对话框上增加缩略图

当在上面的滤镜对话框中使用鼠标拖动或者键盘改变文本框数值时,左侧缩略图将会实时更新以反应当前的参数效果。在参数设置对话框中,我模拟了一个Photoshop中常见的UI特性,当你把鼠标悬停在数值文本框的左侧标签上时,光标变为一个拖动箭头的形状,这时按下鼠标,左右拖动,可以看到相应文本框的数据发生变化(这和操作滑杆控件非常类似)。在上面这个对话框中,你能够看到我如何模拟了PS的这种UI效果(在Photoshop看似朴素的外表下,隐藏着非常多让人惊叹的 UI 效果,而这只是它们中的其中一个,向强大的Photoshop致敬!)。

(3)增加一个我们自己定义的“关于对话框”。

Par souci de simplicité, j'ai uniquement affiché une MessageBox dans "À propos". Nous pouvons personnaliser une boîte de dialogue. Ici, j'ai également adopté les suggestions et le style de PS pour les boîtes de dialogue, c'est-à-dire qu'il n'y a pas de barre de titre, pas de boutons et que la position initiale de la boîte de dialogue est au milieu de sa fenêtre parent (la 1/3 supérieur) place. L'utilisateur appuie sur la touche Échap, Entrée ou clique n'importe où avec la souris pour quitter la boîte de dialogue. La boîte de dialogue À propos de mon filtre est la suivante (cliquez sur le menu dans PS : Aide-> À propos du plug-in-> FillRed Filter... ) : 🎜>

C'est une boîte de dialogue ordinaire, mais ce que je Ce que je veux principalement présenter, c'est que lorsque la souris se déplace vers l'URL de mon blog, le curseur se transforme en forme de main (IDC_HAND) et vous pouvez cliquer pour ouvrir l'URL à l'aide du navigateur par défaut. Cela se fait en utilisant les fonctions correspondantes dans l'ensemble de fonctions de rappel de PS. Je vais donc démontrer ici une utilisation standard des suites de rappel 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);
Copier après la connexion


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插件开发的技术细节以及插件种类仍然是非常繁复众多的,有待进一步的研究。

L'une des principales raisons pour lesquelles nous développons des plug-ins Photoshop est que PS est un logiciel important dans le domaine du traitement graphique et ouvre l'interface d'extension du plug-in aux tiers. En tant que développeurs tiers, nous pouvons étendre PS sous forme de plug-ins selon nos propres besoins et conformément aux conventions de PS. Sur la base de l’importante base d’utilisateurs de PS, l’expansion et la recherche seront plus pratiques.

La technologie de base de création de filtres a été introduite, et le reste des travaux se concentrera principalement sur la recherche et l'exploration d'algorithmes de traitement d'image.

Cet exemple est basé sur le développement de programmes Windows basés sur Platform SDK, mais l'accent est mis sur le développement de plug-ins PS, donc certains détails techniques du développement de programmes Windows ne sont pas expliqués en détail.

Pour plus d'informations sur la façon d'écrire un filtre Photoshop - en ajoutant des vignettes à la boîte de dialogue, veuillez faire attention au site Web PHP chinois pour les articles connexes !

Étiquettes associées:
source:php.cn
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal