Maison > interface Web > Tutoriel PS > le corps du texte

Filtre d'effet de peinture à l'huile Photoshop

高洛峰
Libérer: 2017-02-18 13:33:16
original
2451 Les gens l'ont consulté

Ce filtre a été développé par moi à l'aide du PS SDK. On ne sait peut-être pas qui a proposé l'algorithme de filtre. J'ai fait référence au code source de FilterExplorer (VC 6). La principale source de référence pour cet algorithme est ce projet. , par Jason Waltman (18 avril 2001). De plus, l'algorithme du filtre de peinture à l'huile de PhotoSprite (version 3.0, 2006, écrit par Lian Jun), un autre logiciel domestique écrit en langage C#, doit également être cité du premier (ou d'un autre code homologue). Lors de l'étude de cet algorithme de filtre, je me suis principalement référé au code C du premier. La description conceptuelle de l'algorithme dans cet article appartient à ma compréhension et à mon interprétation. Mais l'efficacité de cet algorithme n'est pas élevée. J'ai grandement amélioré l'efficacité de cet algorithme. La complexité temporelle concernant la taille du modèle a été améliorée de O (n^2) à la complexité linéaire O (n), et la complexité concernant la taille du modèle est passée de O (n^2) à la complexité linéaire O (n). le nombre de pixels est une constante. Le coefficient est considérablement réduit et la vitesse de traitement du même paramètre pour le même échantillon de test (une certaine image RVB de 1920 * 1200 pixels) est réduite d'environ 35 secondes à environ 3 secondes, et le traitement la vitesse est augmentée jusqu'à environ 10 à 12 fois (estimation approximative).

Cet article publie principalement le filtre d'effet de peinture à l'huile Photoshop (OilPaint). L'algorithme n'est pas proposé par moi, vous pouvez vous référer aux références dans cet article. Ce filtre est visible dans le logiciel domestique PhotoSprite développé en C#. On m'a demandé d'aider à développer ce filtre en 2010, et maintenant j'ai passé quelques jours à le développer et à le rendre disponible gratuitement.

(1) Description conceptuelle de l'algorithme du filtre de peinture à l'huile

C'est la compréhension que j'ai acquise en lisant le code source de FilterExplorer. Ce filtre a deux paramètres, l'un est le rayon du modèle (rayon), puis la taille du modèle est la taille (rayon * 2 1) * (rayon * 2 1), c'est-à-dire avec le pixel actuel comme centre, s'étend vers l'extérieur par rayon pixels Zone rectangulaire, en tant que plage de recherche, nous l'appelons temporairement "modèle" (en fait, cet algorithme n'est pas une méthode de modèle standard telle que le flou gaussien ou le filtre personnalisé, c'est juste que le processus de traitement est similaire, je peux donc l'implémenter plus tard optimisation introduite).

Un autre paramètre est la douceur, qui est en fait le nombre de compartiments en niveaux de gris. Nous supposons que l'échelle de gris/luminosité des pixels (0 ~ 255) est divisée uniformément en intervalles de douceur, puis nous appelons ici chaque intervalle un bucket, de sorte que nous ayons de nombreux buckets, temporairement appelés buckets Array (buckets).

Cet algorithme parcourt chaque pixel de l'image et, pour le pixel de position actuelle (x, y), met en niveaux de gris tous les pixels dans la plage du modèle, c'est-à-dire transforme l'image en une image en niveaux de gris, puis modifie la valeur du pixel Une discrétisation supplémentaire consiste à placer les pixels du modèle dans les compartiments correspondants tour à tour en fonction de l'intervalle dans lequel se situe le niveau de gris du pixel. Recherchez ensuite le compartiment contenant le plus grand nombre de pixels et faites la moyenne de la couleur de tous les pixels de ce compartiment comme valeur résultante à la position (x, y).

La description de l'algorithme ci-dessus est représentée par le diagramme schématique suivant. L'image du milieu est le résultat d'une discrétisation des niveaux de gris à partir de l'image originale (équivalente à la séparation des tons dans Photoshop), et la petite case représente le modèle. Ce qui est montré ci-dessous est le tableau de compartiments (8 compartiments, c'est-à-dire que la valeur de gris de 0 à 255 est discrétisée en 8 intervalles).

  Photoshop 油画效果滤镜

  (2) Amélioration de l'efficacité des codes des étrangers existants

Il n'est pas difficile de transplanter le code existant dans le filtre PS tel quel. Il m'a fallu environ 1 à 2 jours de temps libre pour le déboguer avec succès. Mais en lisant le code source des étrangers, j'ai clairement senti que le code original n'était pas assez efficace. Cet algorithme peut être complété en parcourant l'image une fois, et le traitement de chaque pixel est en temps constant, donc la complexité est O(n) pour le nombre de pixels (longueur de l'image * largeur de l'image), mais le coefficient constant du code d'origine est plus grand, par exemple, chaque fois que le résultat du pixel est calculé, l'échelle de gris du pixel dans la plage du modèle doit être recalculée et placée dans le compartiment, ce qui entraîne en fait un grand nombre de calculs répétitifs.

 2.1 À cette fin, ma première amélioration consiste à mettre en niveaux de gris et à discrétiser l'intégralité du patch d'image actuel dans PS (le mettre dans un bucket), de sorte que lors de l'utilisation du modèle lors de la traversée du patch , il n'est pas nécessaire de calculer à plusieurs reprises l'échelle de gris et de la discrétiser. Cela double à peu près la vitesse d'exécution de l'algorithme (pour un certain échantillon, la vitesse de traitement passe de plus de 20 secondes à environ 10 secondes).

 2.2 Mais l'amélioration de la vitesse n'est toujours pas assez significative. J'ai donc procédé à une autre optimisation plus importante, qui consistait à réduire la complexité de la taille du modèle de la complexité carrée à la complexité linéaire. Ceci est basé sur le fait que, étant donné que le modèle se déplace de gauche à droite entre les lignes actuelles, les statistiques des pixels au milieu du modèle (l'intersection de deux modèles adjacents) dans le résultat restent inchangées. Seule la colonne la plus à gauche sort du modèle et la colonne la plus à droite entre dans le modèle. Nous n'avons donc pas à nous soucier des pixels au milieu du modèle lors du parcours de l'image, nous n'avons besoin que de traiter les deux bords du modèle. modèle. Comme indiqué ci-dessous (le rayon est de 2, la taille du modèle est de 5 * 5 pixels) :

 Photoshop 油画效果滤镜

Lorsque l'on atteint le bord droit du patch, on ne le remet pas au début de la ligne comme un retour chariot ou un saut de ligne, mais on déplace le modèle vers le bas d'une ligne et entrez La queue de la ligne suivante, puis traduisez vers la gauche, de sorte que la trajectoire du modèle devienne une trajectoire à pas sinueux. Après cette amélioration, lorsque l'on parcourt les pixels, il suffit de traiter les deux pixels de bord du modèle. De cette façon, la taille du modèle (rayon dans le paramètre) est réduite de O(n^2) à O(n), améliorant ainsi considérablement la vitesse de fonctionnement de l'algorithme. Combinée avec l'optimisation 2.1, la vitesse de fonctionnement de l'algorithme est finalement améliorée 11. fois (cette valeur n'est qu'une estimation approximative et n'a pas été testée sur un grand nombre d'échantillons). Le temps de traitement de l'algorithme optimisé pour les grandes images est également devenu acceptable.

[Remarque] La raison pour laquelle je peux réaliser une telle optimisation est que l'algorithme de filtre n'est pas un algorithme de modèle standard. Son essence est d'obtenir des informations statistiques dans la plage du modèle, c'est-à-dire. le résultat Cela n'a rien à voir avec les coordonnées du modèle du pixel. C'est exactement comme si nous souhaitions obtenir des informations telles que le nombre d'habitants et le ratio hommes/femmes dans une certaine zone locale. Par conséquent, nous optimisons selon la méthode ci-dessus.

La trajectoire de mouvement du modèle est une étape serpentine, par exemple :

→ → → → → → →

 ← ← ← ← ← ← ←

 ↓

 → ...

  Je vais donner ceci ci-dessous Le code de l'algorithme de base du filtre, tous les codes situés dans algorithm.cpp :

code_FilterData_OilPaint

#include "Algorithm.h"

//=========================================
//    缩略图和实际处理共享的滤镜算法
//=========================================
//
// 默认把数据当作是RGB, GrayData 是单通道数据,矩形和 InRect 一致
//
// bInitGray: 是否需要算法重新计算灰度数据
// rowBytes: inData/outData, 扫描行宽度
// colBytes: inData/outData, 对于interleave分布,等于通道数,集中分布时该为1
// planeBytes: 每个通道的字节数(对于interleave分布,该参数的值为1)
// grayData: 由于仅一个通道,所以grayColumnBytes一定是1;
// buckets: 灰度桶; 每个灰度占据4个UINT,0-count,1-redSum,2-greenSum,3-blueSum
// abortProc: 用于测试是否取消的回调函数(在滤镜处理过程中,即测试用户是否按了Escape)
//              在缩略图中用于测试是否已经产生了后续的Trackbar拖动事件
// retVal:如果没有被打断,返回TRUE,否则返回FALSE(说明被用户取消或后续UI事件打断)
//

BOOL FilterData_OilPaint(
    uint8* pDataIn, Rect& inRect, int inRowBytes, int inColumnBytes, int inPlaneBytes,
    uint8* pDataOut, Rect& outRect, int outRowBytes, int outColumnBytes, int outPlaneBytes,
    uint8* pDataGray, int grayRowBytes, BOOL bInitGray,
int radius, 
int smoothness,
    UINT* buckets,
    TestAbortProc abortProc
    )
{
int indexIn, indexOut, indexGray, x, y, i, j, i2, j2, k;    //像素索引
     uint8 red, green, blue;

//设置边界
     int imaxOut = (outRect.right - outRect.left);
int jmaxOut = (outRect.bottom - outRect.top);
int imaxIn = (inRect.right - inRect.left);
int jmaxIn = (inRect.bottom - inRect.top);

//获取两个矩形(inRect和outRect)之间的偏移,即 outRect 左上角在 inRect 区中的坐标
     int x0 = outRect.left - inRect.left;
int y0 = outRect.top - inRect.top;

// 灰度离散化应该作为原子性操作,不应该分割
     if(bInitGray)
    {
//把 In 贴片灰度化并离散化
         double scale = smoothness /255.0;

for(j =0; j < jmaxIn; j++)
        {
for(i =0; i < imaxIn; i++)
            {
                indexIn = i * inColumnBytes + j * inRowBytes; //源像素[x, y]
                
                red = pDataIn[indexIn];
                green = pDataIn[indexIn + inPlaneBytes];
                blue = pDataIn[indexIn + inPlaneBytes*2];

                pDataGray[grayRowBytes * j + i] = (uint8)(GET_GRAY(red, green, blue) * scale);
            }
        }
    }

if(abortProc != NULL && abortProc())
return FALSE;

// 模板和统计数据
// 灰度桶 count, rSum, gSum, bSum
//
    memset(buckets, 0, (smoothness +1) *sizeof(UINT) *4);

int colLeave, colEnter, yMin, yMax;
int rowLeave, rowEnter, xMin, xMax;
int direction;

//初始化第一个模板位置的数据
     yMin = max(-y0,              -radius);
    yMax = min(-y0 + jmaxIn -1, radius);
    xMin = max(-x0,              -radius);
    xMax = min(-x0 + imaxIn -1, radius);

for(j2 = yMin; j2 <= yMax; j2++)
    {
for(i2 = xMin; i2 <= xMax; i2++)
        {
            indexIn = (j2 + y0) * inRowBytes + (i2 + x0) * inColumnBytes;
            indexGray = (j2 + y0) * grayRowBytes + (i2 + x0);

            buckets[ pDataGray[indexGray] *4 ]++; //count
            buckets[ pDataGray[indexGray] *4+1 ] += pDataIn[indexIn]; //redSum
            buckets[ pDataGray[indexGray] *4+2 ] += pDataIn[indexIn + inPlaneBytes]; //greenSum
            buckets[ pDataGray[indexGray] *4+3 ] += pDataIn[indexIn + inPlaneBytes*2]; //greenSum
        }
    }

if(abortProc != NULL && abortProc())
return FALSE;

//进入模板的蛇形迂回循环
    
for(j =0; j < jmaxOut; j++)
    {
if(abortProc != NULL && abortProc())
return FALSE;

//direction:水平移动方向( 1 - 向右移动; 0 - 向左移动)
         direction =1- (j &1);

//找到最大的那个像素
         GetMostFrequentColor(buckets, smoothness, &red, &green, &blue);
if(direction)
        {
            indexOut = j * outRowBytes;
        }
else
        {
            indexOut = j * outRowBytes + (imaxOut -1) * outColumnBytes;
        }
        pDataOut[ indexOut                     ] = red;  
        pDataOut[ indexOut + outPlaneBytes     ] = green;
        pDataOut[ indexOut + outPlaneBytes *2 ] = blue;

        i = direction?1 : (imaxOut -2);

for(k =1; k < imaxOut; k++) //k 是无意义的变量,仅为了在当前行中前进
         {
//每 64 个点测试一次用户取消 ( 在每行中间有一次测试 )
            if((k &0x3F) ==0x3F&& abortProc != NULL && abortProc())
            {
return FALSE;
            }

if(direction) //向右移动
              {
                colLeave = i - radius -1;
                colEnter = i + radius;
            }
else//向左移动
              {
                colLeave = i + radius +1;
                colEnter = i - radius;
            }

            yMin = max(-y0,              j - radius);
            yMax = min(-y0 + jmaxIn -1, j + radius);

//移出当前模板的那一列
              if((colLeave + x0) >=0&& (colLeave + x0) < imaxIn)
            {
for(j2 = yMin; j2 <= yMax; j2++)
                {
                    indexIn = (j2 + y0) * inRowBytes + (colLeave + x0) * inColumnBytes;
                    indexGray = (j2 + y0) * grayRowBytes + (colLeave + x0);

                    buckets[ pDataGray[indexGray] *4     ]--; //count
                    buckets[ pDataGray[indexGray] *4+1 ] -= pDataIn[indexIn]; //redSum
                    buckets[ pDataGray[indexGray] *4+2 ] -= pDataIn[indexIn + inPlaneBytes]; //greenSum
                    buckets[ pDataGray[indexGray] *4+3 ] -= pDataIn[indexIn + inPlaneBytes*2]; //greenSum
                }
            }

//进入当前模板的那一列
              if((colEnter + x0) >=0&& (colEnter + x0) < imaxIn)
            {
for(j2 = yMin; j2 <= yMax; j2++)
                {
                    indexIn = (j2 + y0) * inRowBytes + (colEnter + x0) * inColumnBytes;
                    indexGray = (j2 + y0) * grayRowBytes + (colEnter + x0);

                    buckets[ pDataGray[indexGray] *4     ]++; //count
                    buckets[ pDataGray[indexGray] *4+1 ] += pDataIn[indexIn]; //redSum
                    buckets[ pDataGray[indexGray] *4+2 ] += pDataIn[indexIn + inPlaneBytes]; //greenSum
                    buckets[ pDataGray[indexGray] *4+3 ] += pDataIn[indexIn + inPlaneBytes*2]; //greenSum
                }
            }

//找到最大的那个像素
              GetMostFrequentColor(buckets, smoothness, &red, &green, &blue);

//目标像素[i, j]
            indexOut =  j * outRowBytes + i * outColumnBytes; 
            pDataOut[ indexOut                     ] = red;  
            pDataOut[ indexOut + outPlaneBytes     ] = green;
            pDataOut[ indexOut + outPlaneBytes *2 ] = blue;

            i += direction?1 : -1;
        }

//把模板向下移动一行
         rowLeave = j - radius;
        rowEnter = j + radius +1;

if(direction)
        {
            xMin = max(-x0,              (imaxOut -1) - radius);
            xMax = min(-x0 + imaxIn -1, (imaxOut -1) + radius);
            indexOut = (j +1) * outRowBytes + (imaxOut -1) * outColumnBytes; //目标像素[i, j]
        }
else
        {
            xMin = max(-x0,              -radius);
            xMax = min(-x0 + imaxIn -1, radius);
            indexOut = (j +1) * outRowBytes; //目标像素[i, j]
        }

//移出当前模板的那一列
         if((rowLeave + y0) >=0&& (rowLeave + y0) < jmaxIn)
        {
for(i2 = xMin; i2 <= xMax; i2++)
            {
                indexIn = (rowLeave + y0) * inRowBytes + (i2 + x0) * inColumnBytes;
                indexGray = (rowLeave + y0) * grayRowBytes + (i2 + x0);

                buckets[ pDataGray[indexGray] *4     ]--; //count
                buckets[ pDataGray[indexGray] *4+1 ] -= pDataIn[indexIn]; //redSum
                buckets[ pDataGray[indexGray] *4+2 ] -= pDataIn[indexIn + inPlaneBytes]; //greenSum
                buckets[ pDataGray[indexGray] *4+3 ] -= pDataIn[indexIn + inPlaneBytes*2]; //greenSum
            }
        }

//进入当前模板的那一列
         if((rowEnter + y0) >=0&& (rowEnter + y0) < jmaxIn)
        {
for(i2 = xMin; i2 <= xMax; i2++)
            {
                indexIn = (rowEnter + y0) * inRowBytes + (i2 + x0) * inColumnBytes;
                indexGray = (rowEnter + y0) * grayRowBytes + (i2 + x0);

                buckets[ pDataGray[indexGray] *4     ]++; //count
                buckets[ pDataGray[indexGray] *4+1 ] += pDataIn[indexIn]; //redSum
                buckets[ pDataGray[indexGray] *4+2 ] += pDataIn[indexIn + inPlaneBytes]; //greenSum
                buckets[ pDataGray[indexGray] *4+3 ] += pDataIn[indexIn + inPlaneBytes*2]; //greenSum
            }
        }
    }
return TRUE;
}

//从灰度桶阵列中,提取出最多像素的那个桶,并把桶中像素求平均值作为 RGB 结果。
void GetMostFrequentColor(UINT* buckets, int smoothness, uint8* pRed, uint8* pGreen, uint8* pBlue)
{
    UINT maxCount =0;
int i, index =0;
for(i =0; i <= smoothness; i++)
    {
if(buckets[ i *4 ] > maxCount)
        {
            maxCount = buckets[ i *4 ];
            index = i;
        }
    }
if(maxCount >0)
    {
*pRed =   (uint8)(buckets[ index *4+1 ] / maxCount);    //Red  
        *pGreen = (uint8)(buckets[ index *4+2 ] / maxCount);    //Green 
        *pBlue =  (uint8)(buckets[ index *4+3 ] / maxCount);    //Blue
    }
}
Copier après la connexion

 2.3 Le code d'origine limite la plage du rayon à (1 ~ 5) Depuis que j'ai optimisé le code, je peux augmenter considérablement la plage du rayon que je définis. rayon à À 100, j'ai trouvé qu'il ne servait à rien d'avoir un rayon trop grand, car il était presque impossible de voir quelle était l'image originale.

[Résumé] Le code amélioré est plus technique et plus exigeant, incluant un grand nombre d'opérations de pointeur de bas niveau et entre différents rectangles (patchs d'entrée, patchs de sortie, modèles) Le positionnement des coordonnées peut légèrement réduire la lisibilité du code, mais tant que les principes ci-dessus sont compris, le code aura toujours une bonne lisibilité. De plus, j'ai également pensé à améliorer l'algorithme, en changeant le modèle d'un rectangle en un "cercle", et en faisant trembler les deux paramètres du rayon du modèle et le nombre de seaux de manière aléatoire lors du parcours de l'image, mais ces améliorations rendront le amélioration en 2.2 Si l'optimisation échoue, la vitesse de l'algorithme retombera à un niveau inférieur.

(3) Utiliser la technologie multi-threading pour améliorer l'efficacité de l'affichage des vignettes et éviter d'affecter l'interactivité du fil de l'interface utilisateur

Affichage sur le paramètre boîte de dialogue Pour la technologie des vignettes, veuillez vous référer au quatrième article de mon précédent tutoriel sur l'écriture de filtres PS, que je ne décrirai pas ici. Ce dont je parle ici, ce sont les améliorations apportées à l'interaction de l'interface utilisateur lors de la mise à jour des vignettes, ainsi que la technologie de zoom et de panoramique.

L'image ci-dessous est la boîte de dialogue de paramétrage qui apparaît lorsque ce filtre est appelé dans Photoshop. L'utilisateur peut faire glisser le contrôle slider (TrackBar, également connu sous le nom de Slider) ou entrer directement dans la zone de texte derrière pour modifier les paramètres. Les vignettes seront mises à jour en temps réel pour refléter les nouveaux paramètres. Dans l'implémentation originale du filtre, j'ai placé le traitement de la mise à jour des vignettes dans le même fil de discussion que l'interface utilisateur de la boîte de dialogue. Cela entraînera le problème suivant. Lorsque l'utilisateur fait glisser rapidement le contrôle du curseur, les paramètres changent rapidement et le thread de l'interface utilisateur peut être occupé à traiter les données miniatures et est « bloqué » pendant une courte période, ce qui le rend incapable de répondre immédiatement. aux événements de contrôle ultérieurs, c'est-à-dire que le glissement du contrôle du curseur n'est pas assez fluide, avec des sauts, de la frustration et de la lenteur, et le retour au glissement de la souris n'est pas assez sensible.

 Photoshop 油画效果滤镜

Afin d'améliorer ce problème et de ne pas affecter le fil de discussion de l'interface utilisateur, je prévois de mettre le traitement fastidieux des vignettes tâche sur Terminez-le dans un nouveau fil de discussion Lorsque le fil de discussion termine le traitement des vignettes, il demandera à la boîte de dialogue de mettre à jour sa vue. Lorsque vous faites glisser la Trackbar, le thread de l'interface utilisateur recevra des notifications de contrôle à une fréquence très élevée, comme une "surtension", ce qui nécessite que les événements ultérieurs de l'interface utilisateur arrivant puissent entraîner la fin et la sortie rapide de la tâche du thread en cours d'exécution.

J'ai extrait l'algorithme de filtre en tant que fonction partagée, afin que le traitement réel du filtre et la mise à jour de la vignette puissent partager cette fonction. Lors de l'appel proprement dit du filtre par PS et de la mise à jour de la vignette, l'algorithme du filtre est en effet amené à détecter régulièrement des événements « d'annulation de tâche ». Par exemple, lorsque PS appelle un filtre, si l'utilisateur appuie sur la touche ESC, une opération de filtrage fastidieuse sera immédiatement abandonnée. Lors de la mise à jour des vignettes, si une vague d'événements d'interface utilisateur se produit, le thread de traitement doit également pouvoir se terminer rapidement.

Dans la fonction d'algorithme de base du filtre, je détecte régulièrement des événements "d'annulation de tâche". Puisque la méthode d'annulation de test est différente lors de l'appel du filtre dans PS et lors de la mise à jour de la vignette, l'algorithme de filtre Dans la fonction, J'ai ajouté un paramètre de fonction de rappel (TestAbortProc). De cette façon, lorsque PS appelle le filtre pour un traitement réel, la fonction de rappel intégrée de PS est utilisée pour détecter l'événement d'annulation. Lors de la mise à jour de la vignette de la boîte de dialogue, j'utilise une fonction de rappel fournie par moi-même pour détecter l'événement d'annulation ( cette fonction détecte une variable booléenne pour savoir s'il y a de nouveaux événements UI en attente de traitement).

J'utilise un seul fil pour le traitement des vignettes. Autrement dit, chaque fois qu'un nouvel événement d'interface utilisateur arrive, il est nécessaire de détecter si le thread de traitement des vignettes est en cours d'exécution. Si c'est le cas, je définis une marque pour le nouvel événement d'interface utilisateur, puis j'attends que le thread se termine, puis je commencerai. un nouveau thread après la sortie du thread précédent, de sorte qu'il n'y ait toujours qu'un seul thread pour traiter les vignettes, au lieu d'ouvrir trop de threads lorsque les événements de l'interface utilisateur arrivent en continu. L'avantage est que la logique est claire et facile à contrôler, et cela ne nous laissera pas tomber dans une situation inmaintenable avec trop de threads en difficulté. L'inconvénient est que même si le thread détecte régulièrement les événements d'annulation, il faut encore un peu de temps pour terminer le thread. Cela provoque encore une légère "pause" dans le thread de l'interface utilisateur, mais c'est trivial, comparé à la mise à jour de la vignette dans. le fil de l’interface utilisateur. Réalisez des améliorations essentielles.

Après l'amélioration, vous pouvez faire glisser les deux curseurs sur la boîte de dialogue des paramètres à une vitesse très rapide. Bien que l'algorithme de base de ce filtre nécessite une grande quantité de calculs, nous pouvons voir que la boîte de dialogue des paramètres reste. a de nombreuses fonctions.

(4) Fonctions de zoom et de panoramique des vignettes

En fait, quel que soit le zoom ou le panoramique, il n'est pas difficile de mettre à jour les données des vignettes lui-même. La difficulté réside principalement dans le panoramique des vignettes, car cela implique une interaction avec la souris, ce qui nécessite de très solides compétences en programmation Windows et une compréhension du mécanisme sous-jacent des programmes Windows.

Il existe deux façons de faire glisser les vignettes :

4.1 Faites glisser directement l'image du résultat.

Ceci est divisé en deux méthodes. L’un est un effet de traînée relativement parfait, mais cela se fait au prix d’une certaine perte d’espace et de temps, et le codage est également un défi. Autrement dit, les données d'entrée de la vignette sont agrandies jusqu'à 9 fois leur taille et l'image résultante est obtenue dans la mémoire. Lorsqu'il est affiché, seule la partie centrale du graphique de résultat est affichée. Lors du glisser, aucun espace vide n'apparaît dans la vignette.

Une autre méthode consiste à prendre un instantané (capture d'écran) de l'image du résultat actuel tout en faisant glisser, puis à coller simplement le résultat de la capture d'écran à la position correspondante sur l'écran tout en faisant glisser. C'est plus efficace, mais l'inconvénient est que vous pouvez voir un espace vide à côté de la vignette lorsque vous faites glisser. Cette méthode est souvent utilisée lorsque le coût de mise à jour de la vue est important, comme pour le dessin vectoriel. La méthode que j'ai implémentée dans ce filtre entre dans cette catégorie.

 4.2 Faites glisser l'image vers l'image d'entrée d'origine.

Autrement dit, lors du glissement, l'image utilisée est les données d'origine au lieu de l'image résultante. C'est également un compromis pour réduire le coût de mise à jour des données. Par exemple, cette méthode est utilisée dans le filtre Flou gaussien intégré à Photoshop. Lorsque vous faites glisser la vignette, la vignette affichée est l'image originale et l'effet d'aperçu ne s'affiche qu'après le relâchement de la souris. C'est plus économique et efficace. Parce que le coût pour nous de demander les données originales n'est pas élevé, mais le coût du traitement de la vignette une fois avec un filtre est élevé.

Voici quelques détails techniques supplémentaires. Veuillez noter que comme la souris peut se déplacer hors de la zone client (devenir un nombre négatif), vous ne pouvez pas utiliser directement LOWORD (lParam) et HIWORD (lParam) pour obtenir la zone client. coordonnées. (Parce que WORD est un nombre non signé), ils doivent être convertis en nombres signés (courts) avant utilisation. La bonne méthode consiste à utiliser les macros du fichier d'en-tête windowsx.h : GET_X_LPARAM et GET_Y_LPARAM.

(5) Lien de téléchargement pour ce filtre (la pièce jointe contient l'outil d'installation du plug-in PS que j'ai écrit, qui peut simplifier l'installation de l'utilisateur)

[Ce plug- in a été récemment publié en 2013 -Mise à jour 4-1, performances d'interaction améliorées avec l'interface utilisateur】

   //La dernière collection de plug-ins PS que j'ai développés (y compris ICO, OilPaint, DrawTable, etc.)

                                                                                                                                                                                                                                  com/hood. lum1980/PsPlugIns_V2013.zip

Après installation et redémarrage de Photoshop :

Appelez ce filtre dans le menu : Filtre - hoodlum1980 - OilPaint.

Vous pouvez voir la boîte de dialogue À propos dans le menu : Aide - À propos du plug-in - OilPaint... (l'apparence est presque la même que la boîte de dialogue À propos du plug-in au format de fichier ICO que j'ai développé).

Dans le menu : Aide - Informations système, vous pouvez voir si l'élément "OilPaint" a été chargé et ses informations de version.

(6) Quelques instructions supplémentaires moins importantes

6.1 La taille du patch de sortie que j'ai utilisée est de 128 * 128 pixels pendant le processus de traitement miroir. , chaque étape de la barre de progression que vous voyez sur la barre d'état de Photoshop indique l'achèvement d'un correctif de sortie. Le patch d'entrée est généralement plus grand ou égal au patch de sortie, et la taille du patch d'entrée est liée au rayon dans les paramètres de filtre (la distance en pixels par laquelle le rayon du modèle est étendu vers l'extérieur dans quatre directions).

 6.2 Dans l'algorithme du noyau de filtre, afin d'améliorer la sensibilité aux événements d'annulation, je détecte une annulation tous les 16 pixels traités dans la ligne actuelle (index de pixels dans la ligne & 0x0F == 0x0F). L'annulation est également détectée une fois après le traitement de chaque ligne (dans la boucle de colonnes). Cependant, cette fréquence de détection est légèrement trop fréquente. Une fréquence trop élevée peut augmenter le coût des appels de fonction.

 6.3 En utilisant la même image et les mêmes paramètres, j'ai traité mes filtres, FilterExplorer et PhotoSprite séparément, puis je les ai comparés dans Photoshop. Puisque mon algorithme est basé sur le code source de FilterExplorer et est amélioré sur son algorithme, mon algorithme est équivalent à FilterExplorer, mais plus efficace, donc les résultats sont exactement les mêmes. Mais l'effet global de mon filtre et de FilterExplorer est très proche de celui de PhotoSprite, mais les résultats sont légèrement différents. J'ai vérifié le code de PhotoSprite et j'ai découvert que cela était dû à la différence dans l'algorithme de niveaux de gris de l'image (lorsque j'ai ajusté l'algorithme de niveaux de gris de PhotoSprite pour qu'il soit le même que celui de FilterExplorer, les résultats du traitement sont devenus les mêmes).

 Dans PhotoSprite, la méthode utilisée pour les pixels en niveaux de gris est :
 gray = (byte) ( ( 19661 * R 38666 * G 7209 * B ) >> 16 ) ;

Dans FilterExplorer / le filtre que j'ai développé, la méthode utilisée pour échelle de gris les pixels est :
gray = (byte) (0.3 * R 0.59 * G 0.11 * B);

La méthode des niveaux de gris dans PhotoSprite convertit la multiplication à virgule flottante en multiplication entière. L'efficacité peut être légèrement améliorée, mais les performances ici ne sont pas significatives.

(7) Documents de référence

7.1 Code source de FilterExplorer.

 7.2 Documents du SDK Photoshop 6.0.

 7.3 Code source des plug-ins FillRed et IcoFormat, par hoodlum1980 (moi-même).

(8) Historique des corrections

8.01 [H] Correction du passage du filtre lors de l'allocation de la mémoire du compartiment en niveaux de gris dans l'appel Continuer La taille de la mémoire est incorrecte (incorrectement défini sur la taille de l'espace du bitmap en niveaux de gris) BUG. Ce BUG se déclenche facilement dans les conditions suivantes : la taille du document est trop petite, ou le paramètre de rayon est petit et le paramètre de douceur est grand. Ces facteurs peuvent rendre un patch trop petit. À ce stade, étant donné que l'allocation de l'espace gris du compartiment est inférieure à la taille réelle requise, le code suivant peut dépasser la limite de mémoire, provoquant l'arrêt inattendu du processus PS. 2011-1-19 19:01.

 8.02 [M] Ajout d'un nouveau bouton et d'une nouvelle fonction de zoom miniature, et optimisation du code pour les boutons de zoom avant et arrière afin de réduire le scintillement lors du zoom avant et arrière. 2011-1-19 19:06.

 8.03 [M] Ajout de la fonction de déplacement des vignettes de la souris et ajustement supplémentaire du code pour corriger le rectangle de la vignette, évitant complètement le scintillement lors du zoom. 2011-1-19 23h50.

 8.04 [L] Nouvelle fonctionnalité, lorsque la souris est déplacée sur la vignette et glissée-déposée, le curseur se transforme en une forme de main tendue/saisie. Ceci est réalisé en appelant la fonction dans la suite Suite PEA UI Hooks dans PS, c'est-à-dire que ce que vous voyez sur la vignette est le curseur à l'intérieur de PS. 20/01/2011 1:23.

 8.05 [M] Correction du bug selon lequel le paramètre de rayon était incorrectement converti en cliquant sur le bouton zoom avant ou zoom arrière dans la boîte de dialogue des paramètres. Ce BUG provoque un affichage incorrect des vignettes après avoir cliqué sur le bouton de zoom avant ou arrière. 20/01/2011 1:55.

 8.06 [L] Ajustez le lien URL de la boîte de dialogue À propos de en un contrôle SysLink, ce qui peut grandement simplifier le code de procédure de fenêtre de la boîte de dialogue À propos. 20/01/2011 18h12.

 8.07 [L] Mettez à jour l'outil d'assistance à l'installation des plug-ins afin qu'il puisse installer plusieurs plug-ins à la fois. 20/01/2011 21h15.

 8.08 [L] En raison d'erreurs de calcul lors de la mise à l'échelle des vignettes (la raison est inconnue, peut-être en raison d'erreurs de calcul en virgule flottante), l'image dans le coin inférieur droit près du bord peut ne pas être traduite en vue vignette , donc la traduction est Ajout d'un tampon d'erreur à la plage. 2011-1-27.

 8.09 [L] Embellissement : modifiez le bouton de zoom dans la boîte de dialogue des paramètres de filtre pour utiliser la technologie DirectUI (ajout d'une nouvelle classe CImgButton), l'effet d'interface est meilleur que l'utilisation du contrôle de bouton d'origine. 2011-2-14.

 8.10 [M] Performance : ajustement des performances interactives du curseur pour ajuster les paramètres de rayon et de douceur lorsque le plug-in est dans la boîte de dialogue des paramètres. 2013-4-1.


Pour plus d'articles sur les filtres d'effets de peinture à l'huile Photoshop, veuillez faire attention au site Web PHP chinois !


É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
À propos de nous Clause de non-responsabilité Sitemap
Site Web PHP chinois:Formation PHP en ligne sur le bien-être public,Aidez les apprenants PHP à grandir rapidement!